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.
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!"
}
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.
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With