Chaining LiveData like RxJava with Kotlin extension

Henry Tao
Google Developer Experts
2 min readMay 18, 2018

--

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!

--

--

Henry Tao
Google Developer Experts

Code-Eat-Code, Android, Node, ReactNative, Google Developer Expert in Android, Android@Shopify