Как реализовать функцию zip с помощью LiveData

Я использую два LiveData для получения данных с моего сервера и хочу получить результат после завершения обоих LiveData?

LiveData live1 = ...;
LiveData live2 = ...;
MutableLiveData live3 = ...;

live1.observe(this, value -> {
    live3.postValue(value);
});

live2.observe(this, value -> {
   live3.postValue(value);
});

live3.observe(this,  value -> {
// TODO: Get both values from live1, live2
}

Я ожидаю оба значения от live1 и live2

В чем проблема? Что происходит в live3.observe?

HB. 28.07.2019 07:22

Используйте MediatorLiveData для слияния обоих потоков.

Ehsan Aminifar 28.07.2019 10:34
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
2
2 087
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

То, что вам нужно, называется функцией zip:

fun <A, B> zip(first: LiveData<A>, second: LiveData<B>): LiveData<Pair<A, B>> {
    val mediatorLiveData = MediatorLiveData<Pair<A, B>>()

    var isFirstEmitted = false
    var isSecondEmitted = false
    var firstValue: A? = null
    var secondValue: B? = null

    mediatorLiveData.addSource(first) {
        isFirstEmitted = true
        firstValue = it
        if (isSecondEmitted) {
            mediatorLiveData.value = Pair(firstValue!!, secondValue!!)
            isFirstEmitted = false
            isSecondEmitted = false
        }
    }
    mediatorLiveData.addSource(second) {
        isSecondEmitted = true
        secondValue = it
        if (isFirstEmitted) {
            mediatorLiveData.value = Pair(firstValue!!, secondValue!!)
            isFirstEmitted = false
            isSecondEmitted = false
        }
    }

    return mediatorLiveData
}


Теперь вы можете вызвать zip(firstLiveData,secondLiveData) и понаблюдать за ним.

Ответ принят как подходящий

Вот более общая версия, которая позволяет вам наблюдать несколько LiveData.

fun zipLiveData(vararg liveItems: LiveData<*>): LiveData<ArrayList<Any>> {
    //MediatorLiveData used to merge multiple LiveDatas
    return MediatorLiveData<ArrayList<Any>>().apply {
        val zippedObjects = ArrayList<Any>()
        liveItems.forEach {
            //Add each LiveData as a source for the MediatorLiveData
            addSource(it) { item ->
                //Add value to list
                item?.let { it1 -> zippedObjects.add(it1) }
                if (zippedObjects.size == liveItems.size) {
                    //If all the LiveData items has returned a value, save that value in MediatorLiveData.
                    value = zippedObjects
                    //Clear the list for next time
                    zippedObjects.clear()
                }
            }
        }
    }
}

Приведенная выше функция не добавляет значения null в список, если вы также хотите добавить значение null, вместо этого вам нужно будет сделать что-то в следующих строках:

fun zipLiveData(vararg liveItems: LiveData<*>): LiveData<ArrayList<Any?>> {
    return MediatorLiveData<ArrayList<Any?>>().apply {
        val zippedObjects = ArrayList<Any?>()
        liveItems.forEach {
            addSource(it) { item ->
                zippedObjects.add(item)
                if (zippedObjects.size == liveItems.size) {
                    value = zippedObjects
                    zippedObjects.clear()
                }
            }
        }
    }
}

Обновлять — я только что понял, что вышеуказанные методы не сохраняют «порядок» элементов LiveData (так, например, если второй LiveData опубликовал значение перед первым, вы получите значение [secondLiveDataValue, firstLiveDataValue] вместо ожидаемого [firstLiveDataValue, secondLiveDataValue]) . Вместо этого вы можете использовать одну из следующих двух функций, если хотите сохранить «порядок» значений элементов LiveData.

//If you know the LiveDatas won't get null values
fun zipLiveData(vararg liveItems: LiveData<*>): LiveData<ArrayList<Any?>> {
    return MediatorLiveData<ArrayList<Any?>>().apply {
        var zippedObjects = arrayOfNulls<Any>(liveItems.size)
        liveItems.forEachIndexed { index, liveData ->
            addSource(liveData) { item ->
                zippedObjects[index] = item
                if (!zippedObjects.contains(null)) {
                    value = zippedObjects.toCollection(ArrayList())
                    zippedObjects = arrayOfNulls(liveItems.size)
                }
            }
        }
    }
}

//Incase your LiveDatas might have null values
fun zipLiveDataWithNull(vararg liveItems: LiveData<*>): LiveData<ArrayList<Any?>> {
    return MediatorLiveData<ArrayList<Any?>>().apply {
        val zippedObjects = arrayOfNulls<Any>(liveItems.size)
        val zippedObjectsFlag = BooleanArray(liveItems.size)
        liveItems.forEachIndexed { index, liveData ->
            addSource(liveData) { item ->
                zippedObjects[index] = item
                zippedObjectsFlag[index] = true
                if (!zippedObjectsFlag.contains(false)) {
                    value = zippedObjects.toCollection(ArrayList())
                    for(i in 0 until liveItems.size) {
                        zippedObjectsFlag[i] = false
                    }
                }
            }
        }
    }
}

Мое решение несколько вдохновлено zip-оператором rxjava,

inline fun <reified I1, I2, O> biZip(inputLiveData1: LiveData<I1>, inputLiveData2: LiveData<I2>, crossinline tranform: (data1: I1, data2: I2) -> O): LiveData<O> {

    var input1: I1? = null
    var input2: I2? = null
    val mediatorLiveData = MediatorLiveData<O>()
    mediatorLiveData.addSource(inputLiveData1) {
        input1 = it
        if (input1 != null && input2 != null) {
            mediatorLiveData.value = tranform.invoke(input1!!, input2!!)
        }
    }
    mediatorLiveData.addSource(inputLiveData2) {
        input2 = it
        if (input1 != null && input2 != null) {
            mediatorLiveData.value = tranform.invoke(input1!!, input2!!)
        }
    }

    return mediatorLiveData
}

теперь вы можете использовать его как,

val liveDataString = MutableLiveData<String>()
val liveDataInt = MutableLiveData<Int>()

val liveDataofTest = biZip<String, Int, Test>(liveDataString, liveDataInt) { data1:String, data2:Int ->
      return@biZip Test(data1, data2)
}

Тестовое pojo похоже на

data class Test(val name:String,value:Int)

Вы можете использовать его напрямую, как функцию расширения с

fun <A, B, C> LiveData<A>.zip(stream: LiveData<B>, func: (source1: A?, source2: B?) -> C): LiveData<C> {
        val result = MediatorLiveData<C>()
        result.addSource(this) { a -> 
            result.setValue(func.invoke(a,stream.value))
        }
        result.addSource(stream) { b -> 
            result.setValue(func.invoke(this.value, b)) 
        }
        return result
}

Эта функция расширения работала для меня

fun <A, B> LiveData<A>.zipWith(stream: LiveData<B>): LiveData<Pair<A, B>> {
 val result = MediatorLiveData<Pair<A, B>>()
  result.addSource(this) { a ->
    if (a != null && stream.value != null) {
        result.value = Pair(a, stream.value!!)
    }
  }
 result.addSource(stream) { b ->
    if (b != null && this.value != null) {
        result.value = Pair(this.value!!, b)
    }
 }
 return result
}

Недооцененный ответ

DennisVA 27.08.2021 16:24

Другие вопросы по теме