Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map multiple LiveData values into one

So, in iOS development, I use ReactiveCocoa and with that framework I am able to observe multiple NSObjects and combine them into a signal that returns a a value. Something like this:

-(RACSignal *)modelIsValidSignal {

    return [RACSignal combineLatest:@[RACObserve(self,username), RACObserve(self,password), RACObserve(self, busyLoggingIn)]
                             reduce:^id(NSString *username, NSString *password, NSNumber *busyLoggingIn) {
                                 return @((username.length > 0) && (password.length > 0 && busyLoggingIn.boolValue == NO));
                             }];
}

So, this will return a boolean that is either false or true. As soon as one of the objects state changes, this signal would be notified and the subscriber (Observer) would then get that current value of the boolean.

How do I do something similar to this, using LiveData? the closest thing to doing this, is MediatorLiveData but, I don't see how I can observe multiple LiveData events at the same time and then reduce it, like in the above example.

like image 323
Robert J. Clegg Avatar asked Aug 16 '17 12:08

Robert J. Clegg


3 Answers

Handy and safe solution:

fun <T1, T2, R> combine(
    liveData1: LiveData<T1>,
    liveData2: LiveData<T2>,
    combineFn: (value1: T1?, value2: T2?) -> R
): LiveData<R> = MediatorLiveData<R>().apply {
    addSource(liveData1) {
        value = combineFn(it, liveData2.value)
    }
    addSource(liveData2) {
        value = combineFn(liveData1.value, it)
    }
}

fun <T1, T2, R> Pair<LiveData<T1>, LiveData<T2>>.map(
    combineFn: (value1: T1?, value2: T2?) -> R
) = combine(first, second, combineFn)

Use example:

val greetLine = combine(nameLiveData, surnameLiveData) { name, surname ->
   "Hello, $name $surname!"
}

// or using map extension function for consistency with LiveData api
val greetLine = (nameLiveData to surnameLiveData).map { name, surname ->
   "Hello, $name $surname!"
}
like image 110
ruX Avatar answered Oct 14 '22 20:10

ruX


I have made some validation like this

var firstName: MutableLiveData<String> = MutableLiveData()
var lastName: MutableLiveData<String> = MutableLiveData()

var personalDetailsValid: MediatorLiveData<Boolean> = MediatorLiveData()

personalDetailsValid.addSource(firstName, {
    personalDetailsValid.value = lastName.value?.isNotBlank() == true && it?.isNotBlank() ?: false
})

personalDetailsValid.addSource(lastName, {
    personalDetailsValid.value = firstName.value?.isNotBlank() == true && it?.isNotBlank() ?: false
})

Then I observe the usual way

viewModel.personalDetailsValid.observe(this, Observer {
        if (it == true) {
            //Do something
        }
    })

I don't know if this is the recommended way of using LiveData and MediatorLiveData but it works for me.

like image 32
ngatirauks Avatar answered Oct 14 '22 20:10

ngatirauks


I've implemented some util methods for 2 (and more) LiveData values. In works in a RX Observable.combineLatest() manner, meaning that it fires updated value if one of the observed LiveData will emit any new value. I've added Function2 as a "combiner" function (in my case I could easily use RX.BiFunction, but LiveData can emit null values).

Example of combining values from two LiveData.

object LiveDataUtils {

    fun <T1, T2, R> combineLatest(source1: LiveData<T1>,
                                  source2: LiveData<T2>,
                                  combiner: Function2<T1, T2, R>): LiveData<R> {
        val mediator = MediatorLiveData<R>()

        val combinerFunction = {
            val source1Value = source1.value
            val source2Value = source2.value
            mediator.value = combiner.apply(source1Value, source2Value)
        }

        mediator.addSource(source1) { combinerFunction() }
        mediator.addSource(source2) { combinerFunction() }
        return mediator
    }

}

interface Function2<T1, T2, R> {
    fun apply(t1: T1?, t2: T2?): R
}
like image 37
Demigod Avatar answered Oct 14 '22 20:10

Demigod