Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 977
Damia Fuentes Avatar asked Nov 30 '17 11:11

Damia Fuentes


2 Answers

You can use switchMap() for such case, because it returns LiveData object which can be Transformations.map()

In below code I am getting sum of final amount of two objects onwardSelectQuote and returnSelectQuote

finalAmount = Transformations.switchMap(onwardSelectQuote) { data1 ->
            Transformations.map(returnSelectQuote) { data2 -> ViewUtils.formatRupee((data1.finalAmount!!.toFloat() + data2.finalAmount!!.toFloat()).toString())
            }
        }
like image 141
Shahbaz Hashmi Avatar answered Oct 06 '22 15:10

Shahbaz Hashmi


I come up with another solution.

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

Then, you can combine multiple source.

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

If you want to have 4 or more sources, you need to create you own data class because Kotlin only has Pair and Triple.

In my opinion, there is no reason to run with uiThread in Damia's solution.

like image 12
Joshua Avatar answered Oct 06 '22 17:10

Joshua