Search code examples
androidandroid-livedataandroid-viewmodel

LiveData Transformations.map() with multiple arguments


I have a value in the UI that it's value depends on two LiveData objects. Imagine a shop where you need a subtotal = sum of all items price and a total = subtotal + shipment price. Using Transformations we can do the following for the subtotal LiveData object (as it only depends on itemsLiveData):

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

In the case of the total it would be great to be able to do something like this:

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

But, unfortunately, that's not possible because we cannot put more than one argument in the map function. Anyone knows a good way of achieving this?


Solution

  • UPDATE

    Based on my previous answer, I created a generic way where we can add as many live datas as we want.

    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)
                }
            }
        }
    }
    

    OLD

    At the end I used MediatorLiveData to achieve the same objective.

    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
    }