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