RxJava VS. Coroutines In Two Use Cases
By: Peike Dai
Tags: Kotlin Coroutines, Kotlin, Android, Rxjava, Coroutine

RxJava has been a lifesaver for me for a long time. With all the functionalities it provides, my Android programming mindset has greatly shifted to a more reactive way. If you look at my code, it’ll be Single , Subject and Completable everywhere.

Now we have Coroutines, which has been given praises and recommendations in many talks and conferences. So I started to learn it.

To show you what I have learned so far, I’ll try to compare how some of the common problems are solved in both RxJava and Coroutines. The libraries and design patterns I’m using are:

MVVM with Android Architecture Component

Repository Pattern

Retrofit

Load And Show

Open the menu Fragment and show a list of coffees fetched from server

RxJava

Let’s begin from the ViewModel side. In the MenuViewModel , I call menuRepository.getMenu() to obtain an instance of Single .

Snippet 1: ViewModel — Load And View

Note that error handling is ignored.

To avoid memory leak, we have to remember disposing of the disposable during the onCleared() .

And on the repository side:

Snippet 2: MenuRepository — Load And View

We are calling subscribeOn and observeOn to make sure the network call is performed not on the main thread.

Coroutines

Snippet 3: ViewModel and Repository with Coroutines For Load And View

As you can see here, with the help of Coroutines and KTX extension, the code becomes so much simpler. The lifecycle and threading are taken cared with just one parameter passed to the liveData block:

context = viewModelScope.coroutineContext + Dispatchers.IO

Update 09/17 — This is actually unnecessary. Please check the comment by Hán Trung Kiên below.

It’s an easy win for Coroutines in this round. But this is hardly going to be the final use case of any app in the market. Let’s add a couple of features.

Update View A From View B

When user chooses a menu type in menu type Fragment, update the coffee menu.

To be more specific, the MenuTypeFragment is placed on top of the MenuFragment . When a user changes the menu type there, we update the menu down below with the selected menu type.

To separate the concern and decouple the components, these two Fragments won’t communicate with each other directly. We’ll utilize the MenuRepository as the middleman and have both Fragments share the same instance of it. This can be achieved by the dependency injection framework like Dagger. The actual implementation of it will be ignored here.

RxJava

Snippet 4: MenuViewModel and MenuRepository for View A to View B

In the MenuRepository , getMenu() is replaced with a menu property and a refreshMenu() method. I use the side-effect operator doOnSuccess() to pass the received data to the Subject , which is a BehaviorSubject that persists the most recent menu data. In the MenuFragment , the getMethod().subscribe {} is split into two calls, menu.subscribe {} and refreshMenu().subscribe() .

With that change, we can simply do this in the MenuTypeFragment .

Snippet 5: MenuTypeFragment for View A to View B

Note that I ignored the disposable handling. You would still need to do the same handling shown in the previous use case to avoid any memory leak. Also, the error handling has to be done on every call of the menuRepo.refreshMenu() should the API call failed.

Coroutines

In this case, Coroutines does not simplify as much as it did in snippet 3. And you will see why.

Snippet 6

In the MenuRepository , we still need to create something that’s similar to the BehaviorSubject . In the Coroutines world, it’s called BroadcastChannel(CONFLATED) . This is a factory method which creates an instance of ConflatedBroadcastChannel . There are other types of BroadcastChannel just like in RxJava there are other types of Subject .

To be able to observe a hot flow of data, we convert the BroadcastChannel to Flow using asFlow . Then we can call collect() in the ViewModel to continuously receive new data.

This concept is much closer to the RxJava world than the Coroutines world. The collect() is like subscribe() , and the Flow is like Observable . And if you take a look at the Flow , there are quite a lot of built-in operators like retryWhen or debounce , which RxJava has already had and we all love.

Then in the MenuTypeFragment

Snippet 7

Because we’re using ConflatedBroadcastChannel , so the send() inside the refreshMenu() will never suspend. That means if you have a lot of places using the refreshMenu() , there won’t be whole a lot of suspended Coroutines holding your resources.

Update 10/30/2020

As the release of Kotlin coroutines 1.4.0, the ConflatedBroadcastChannel is deprecated. The StateFlow and ShareFlow are introduced. You can find more about them in the official announcement.

Which Dispatchers?

In Snippet 6, we directly call emit in collect .

menuRepo.menu.collect(::emit)

But which dispatcher is used to set the value to the LiveData? To figure that out, I print some logs.

menuRepo.menu.collect {

Log.d("MenuViewModel", Thread.currentThread().name)

emit(it)

} // printed: D/MenuViewModel: DefaultDispatcher-worker-1

This doesn’t look right, how is this not crashing the app when I’m not setting the LiveData value in the main thread?

It turns out the KTX library takes care of that for us. Inside emit() of liveData { } , it is doing this:

override suspend fun emit(value: T) = withContext(coroutineContext) {

// target: LiveData

target.clearSource()

target.value = value

}

And you probably guessed, this is the coroutineContext :

private val coroutineContext = context + Dispatchers.Main.immediate

That Dispatchers.Main represents our Android’s Main Thread.

Subscription Disposal?

But what about the disposal of menuRepo.menu.collect() ? Will the subscription get canceled when the LiveData becomes inactive? Or do we still have to do all the ceremonies in RxJava?

The answer is YES, it’ll get canceled automatically. Inside the KTX library, the LiveData generated by liveData { } will cancel the running Coroutines when the LiveData goes inactive.

Summary

Coroutines itself could be a huge lifesaver when you have a blocking time-consuming task that you’re waiting for its result. If you utilize Android KTX libraries, you’ll find yourself writing the most efficient code.

However, when you want to use Coroutines to implement the Observers design pattern, it does not boost as much productivity comparing with the RxJava. In the end, Coroutines were not built specifically for that anyway.

RxJava is still the most powerful tool in my toolbox. I won’t suggest migrating to Coroutines in all scenarios. There are still a lot of experimental APIs in Coroutines, including the BroadcastChannel in my example. It’ll still take time for the Coroutines to really shine.

Happy coding!