【问题标题】:LiveData Transformations.map() with multiple arguments具有多个参数的 LiveData Transformations.map()
【发布时间】:2017-11-30 11:53:45
【问题描述】:

我在 UI 中有一个值,它的值取决于两个 LiveData 对象。想象一下您需要subtotal = sum of all items pricetotal = subtotal + shipment price 的商店。使用Transformations,我们可以对小计LiveData 对象执行以下操作(因为它只依赖于itemsLiveData):

val itemsLiveData: LiveData<List<Items>> = ...
val subtotalLiveData = Transformations.map(itemsLiveData) { 
   items ->
       getSubtotalPrice(items)
}

就总数而言,能够做这样的事情会很棒:

val shipPriceLiveData: LiveData<Int> = ...
val totalLiveData = Transformations.map(itemsLiveData, shipPriceLiveData) { 
   items, price ->
       getSubtotalPrice(items) + price
}

但是,不幸的是,这是不可能的,因为我们不能在 map 函数中放置多个参数。有谁知道实现这一目标的好方法?

【问题讨论】:

    标签: android android-livedata android-viewmodel


    【解决方案1】:

    您可以在这种情况下使用 switchMap(),因为它返回的 LiveData 对象可以是 Transformations.map()

    在下面的代码中,我得到了两个对象 onwardSelectQuotereturnSelectQuote

    的最终数量的总和
    finalAmount = Transformations.switchMap(onwardSelectQuote) { data1 ->
                Transformations.map(returnSelectQuote) { data2 -> ViewUtils.formatRupee((data1.finalAmount!!.toFloat() + data2.finalAmount!!.toFloat()).toString())
                }
            }
    

    【讨论】:

    • 不幸的是,这似乎将一个 LiveData 填充到另一个 LiveData 中。
    • @BrillPappin 幸运的是,它非常适合同时观察两个 LiveData。
    • @BrillPappin 我认为这不是给定场景的问题,switchMap 与他的实现相应地工作:developer.android.com/reference/android/arch/lifecycle/…,我认为这是答案的最佳和更简单的解决方案。
    • 它可能工作,但我觉得它有点草率,可能会导致以后很难找到错误。我个人不会那样写代码。
    【解决方案2】:

    我想出了另一个解决方案。

    class PairLiveData<A, B>(first: LiveData<A>, second: LiveData<B>) : MediatorLiveData<Pair<A?, B?>>() {
        init {
            addSource(first) { value = it to second.value }
            addSource(second) { value = first.value to it }
        }
    }
    
    class TripleLiveData<A, B, C>(first: LiveData<A>, second: LiveData<B>, third: LiveData<C>) : MediatorLiveData<Triple<A?, B?, C?>>() {
        init {
            addSource(first) { value = Triple(it, second.value, third.value) }
            addSource(second) { value = Triple(first.value, it, third.value) }
            addSource(third) { value = Triple(first.value, second.value, it) }
        }
    }
    
    fun <A, B> LiveData<A>.combine(other: LiveData<B>): PairLiveData<A, B> {
        return PairLiveData(this, other)
    }
    
    fun <A, B, C> LiveData<A>.combine(second: LiveData<B>, third: LiveData<C>): TripleLiveData<A, B, C> {
        return TripleLiveData(this, second, third)
    }
    

    然后,您可以组合多个来源。

    val totalLiveData = Transformations.map(itemsLiveData.combine(shipPriceLiveData)) {
        // Do your stuff
    }
    

    如果您想拥有 4 个或更多源,则需要创建自己的数据类,因为 Kotlin 只有 PairTriple

    在我看来,在 Damia 的解决方案中没有理由使用 uiThread

    【讨论】:

    • 你能解释一下你的这部分代码吗?我怎样才能以不同的方式编写它? init { addSource(first) { value = it to second.value } addSource(second) { value = first.value to it } }
    【解决方案3】:

    更新

    根据我之前的回答,我创建了一种通用方式,我们可以根据需要添加任意数量的实时数据。

    import androidx.lifecycle.LiveData
    import androidx.lifecycle.MediatorLiveData
    
    /**
     * CombinedLiveData is a helper class to combine results from multiple LiveData sources.
     * @param liveDatas Variable number of LiveData arguments.
     * @param combine   Function reference that will be used to combine all LiveData data.
     * @param R         The type of data returned after combining all LiveData data.
     * Usage:
     * CombinedLiveData<SomeType>(
     *     getLiveData1(),
     *     getLiveData2(),
     *     ... ,
     *     getLiveDataN()
     * ) { datas: List<Any?> ->
     *     // Use datas[0], datas[1], ..., datas[N] to return a SomeType value
     * }
     */
    class CombinedLiveData<R>(vararg liveDatas: LiveData<*>,
                              private val combine: (datas: List<Any?>) -> R) : MediatorLiveData<R>() {
    
        private val datas: MutableList<Any?> = MutableList(liveDatas.size) { null }
    
        init {
            for(i in liveDatas.indices){
                super.addSource(liveDatas[i]) {
                    datas[i] = it
                    value = combine(datas)
                }
            }
        }
    }
    

    最后我使用MediatorLiveData 来达到同样的目的。

    fun mapBasketTotal(source1: LiveData<List<Item>>, source2: LiveData<ShipPrice>): LiveData<String> {
        val result = MediatorLiveData<String>()
        uiThread {
            var subtotal: Int = 0
            var shipPrice: Int = 0
            fun sumAndFormat(){ result.value = format(subtotal + shipPrice)}
            result.addSource(source1, { items ->
                if (items != null) {
                    subtotal = getSubtotalPrice(items)
                    sumAndFormat()
                }
            })
            result.addSource(source2, { price ->
                if (price != null) {
                    shipPrice = price
                    sumAndFormat()
                }
            })
        }
        return result
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-10
      • 2020-05-27
      • 1970-01-01
      • 1970-01-01
      • 2019-08-31
      • 1970-01-01
      • 1970-01-01
      • 2016-07-21
      相关资源
      最近更新 更多