Chaining LiveData like RxJava with Kotlin extension
If you haven’t read my article about NonNull LiveData, you need to read it. Same idea will be reused here.
If you don’t know about RxJava, you also need to read about it from https://github.com/ReactiveX/RxJava.
In this article, we will talk about how we can borrow some ideas from RxJava and apply them to LiveData. Of course, thanks to Kotlin extension. It helps developer life easier. Sorry Java! The goal will look like this:
val liveData: MutableLiveData<Boolean> = MutableLiveData()
liveData
.distinct()
.filter { it == false }
.map { true }
.nonNull()
.observe(lifecycleOwner, { result ->
// result is non-null and always true
})
The simplest approach
Let’s take distinct
as an example.
val liveData: MutableLiveData<Boolean> = MutableLiveData()
liveData
.distinct()
.observe(lifecycleOwner, { result ->
// new result is different from previous result
})
In order to do as above, you need to create distinct
and observe
extension for LiveData.
fun <T> LiveData<T>.distinct(): LiveData<T> {
val mediatorLiveData: MediatorLiveData<T> = MediatorLiveData()
mediatorLiveData.addSource(this) {
if (it != mediatorLiveData.value) {
mediatorLiveData.value = it
}
}
return mediatorLiveData
}fun <T> LiveData<T>.observe(owner: LifecycleOwner, observer: (t: T?) -> Unit) {
observe(owner, Observer { observer(it) })
}
You can follow the same pattern for other methods like filter
, map
…
Supporting NonNull
Supporting NonNull is a bit tricky. As I mentioned in NonNull LiveData article, you need to have NonNullMediatorLiveData
to differentiate between null and non-null observer. Therefore, you may need to duplicate you logic for each of extension method. Your filter extension will look like this:
fun <T> LiveData<T>.distinct(): LiveData<T> {
val mediatorLiveData: MediatorLiveData<T> = MediatorLiveData()
mediatorLiveData.addSource(this) {
if (it != mediatorLiveData.value) {
mediatorLiveData.value = it
}
}
return mediatorLiveData
}// Exactly same logic but return NonNullMediatorLiveData
fun <T> NonNullMediatorLiveData<T>.distinct(): LiveData<T> {
val mediatorLiveData: NonNullMediatorLiveData<T> = NonNullMediatorLiveData()
mediatorLiveData.addSource(this) {
if (it != mediatorLiveData.value) {
mediatorLiveData.value = it
}
}
return mediatorLiveData
}fun <T> LiveData<T>.observe(owner: LifecycleOwner, observer: (t: T?) -> Unit) {
observe(owner, Observer { observer(it) })
}
Now you can do distinct
and nonNull
together despite the order.
val liveData: MutableLiveData<Boolean> = MutableLiveData()
liveData
.nonNull()
.distinct()
.observe(lifecycleOwner, { result ->
// new result is different from previous result
})// or
liveData
.distinct()
.nonNull()
.observe(lifecycleOwner, { result ->
// new result is different from previous result
})
Polishing code with MediatorObserver
Duplicating logic is not nice. We can improve it by defining interface MediatorObserver<IN, OUT>
. You code will look cleaner like this:
private class DistinctExt<T> : MediatorObserver<T, T> {
override fun run(source: LiveData<T>, mediator: MediatorLiveData<T>, value: T?) {
if (value != mediator.value) {
mediator.value = value
}
}
}
fun <T> LiveData<T>.distinct(): LiveData<T> = createMediator(this, DistinctExt())
fun <T> NonNullMediatorLiveData<T>.distinct(): NonNullMediatorLiveData<T> = createMediator(this, DistinctExt())
For more information, take a look at LiveData.kt.
Ready to use library
You can try out https://github.com/henrytao-me/livedata-ktx. Adding one line of code in your build.gradle and ready to rock:
implementation "me.henrytao:livedata-ktx:LATEST_VERSION"
Feel missing methods?
Please suggest what you need by creating issues. I will support it as fast as I can.
Let me know your thought and happy coding!