Coroutines and RxJava — An Asynchronicity Comparison (Part 7): MVI Project
Introduction
In this blog series I have compared Kotlin Coroutines and RxJava since they are both trying to solve a common problem in Android development: Asynchronous Programming. With the last post, we finished comparing topics between these two libraries with the previous part of the series, now it’s time to put everything we learned into practice.
Let’s build an MVI project using what we learned!
The Android App
In this article, we’ll build an Android app that will calculate the fibonacci of a number and can optionally tell you a fun fact about that number.
We’ll make a network request to calculate the fun fact of the number using a public API called numbersapi.com.
Calculating fibonacci and getting the fun fact are two tasks that will be run in parallel and only when both requests come will we display the information to the user. This is in case the user toggled the “tell me a fun fact” checkbox.
App Variants — Projects
We have three projects variants available:
- Pure Coroutines.
- Pure RxJava.
- Coroutines/RxJava interop. Some parts will be done in RxJava and will be consumed with Coroutines (It could’ve been the other way around as well).
Disclaimer: This code might not be perfect. Pay attention to the high level overview, not in details that you already know how to fix :)
App Overview
We’ll be using an MVI pattern and Architecture Components ViewModel.
- MVI helps us build an app with a clear separation of concerns and an easy way to maintain its state.
- We’ll use Architecture Components ViewModel to manage configuration changes and be able to restore states easily.
Surviving configuration changes is a pain point for every Android developer. I found myself writing an article about this a while back. In a nutshell, we want the state of our app to continue being displayed after the user rotates the screen. See the screenshot below:
MVI Architecture
We will follow the MVI pattern as follows:
The user will interact with the app. The View will react to it and communicates the user input to the ViewModel with a UserAction. The ViewModel will process the action and will communicate back to the View with a ViewState. Then, the View will render the new state and the user will see it.
As you can see, two communications take place here:
- View -> ViewModel with the UserAction. The ViewModel needs to listen for View events and be able to react to them.
- ViewModel -> View with the ViewState. The View needs to listen for ViewModel events and be able to react to them.
We need two entities/objects there that can manage that communication.
MVI in RxJava
The first thing that probably comes to our minds is using Subjects. Why a Subject? We want a Hot Observable that can live even if no Observers listen for events and the ability to broadcast those to multiple Observers.
The ViewModel can have a userActionSubject
and a viewStateSubject
.
- The View calls
onNext
on theuserActionSubject
to send events to the ViewModel. The ViewModel subscribes to it when it initializes. This subject can be of typePublishSubject
. - The ViewModel calls
onNext
on theuserStateSubject
to send events to the View. The View subscribes to it every time it’s visible to the user (we can register inonStart
). This subject should be of typeBehaviorSubject
. We need aBehaviorSubject
because we want the View to automatically receive the most recent ViewState sent by the ViewModel after a configuration change. When that happens, the View will subscribe again to the same subject, and the subject will emit the last state it sent. In this way, the View can restore the ViewState it was.
All the logic to process the UserAction happens in the subscription to the userActionSubject
. Inside that code is where the ViewModel will call onNext
on the userStateSubject
to communicate to the View.
If you realized that in the previous image we said that a Subject is not optimal. Let me clarify this. The ViewModel creates a Subject but it shouldn’t expose it as a Subject object.
- In the first scenario, the View shouldn’t be able to call
onComplete
. If that happens, the Subject will be useless and no more communication can happen after that. We can use ’s RxRelay library and use an PublishRelay. - In this case, the View doesn’t need to send events to the ViewModel, it just needs to register to the Subject. Instead of exposing a Subject, we can expose an Observable and protect the ViewModel.
Watch out for…
- The ViewModel subscribes to the
userActionSubject
onSchedulers.computation()
, we need the processing to happen in the background. - The ViewModel cleans up the Subjects in the
onCleared
method that the ViewModel library provides. - The View observes the events of the
viewStateSubject
onAndroidSchedulers.mainThread()
. - The View starts listening to the
viewStateSubject
inonStart
and disposes the subscription inonStop
.
MVI in Coroutines
We need to achieve the same behavior we programmed in RxJava with Coroutines now.
If we used Subjects for the RxJava implementation, we can use Channels for the Coroutines one. We saw that they’re functionally similar in the third part of this series.
- For the View -> ViewModel communication we can use an
Actor
. Obviously, we want to process the UserAction in the background so we need a Coroutine. For this use case, the ViewModel doesn’t need to send anything back in this channel; it will just listen for events sent by the View. For all these reasons, an Actor is the ideal candidate: Coroutine + Channel that can only receive events. The Actor will process one UserAction at a time in the background. - For the ViewModel -> View communication, the ViewModel creates a
ConflatedBroadcastChannel
. If we used theBehaviorSubject
to reply events when a new subscription comes in in RxJava, we used theConflatedBroadcastChannel
in Coroutines. Exactly for the same reason, so we can survive configuration changes and can restore the state.
The suspending code inside the Actor will process the UserActions and will send events to the ConflatedBroadcastChannel
.
Watch out for…
- The ViewModel runs the Actor on the CommonPool.
- The ViewModel closes the
viewStateChannel
and theuserActionActor
in theonCleared
ViewModel method. - The View creates a new Coroutine using
launch(parentJob + CommonPool)
to start listening to theviewStateChannel
with theconsumeEach
extension function. In that code, we callwithContext(UI)
so the events are processed on the Android UI Main Thread. - The View starts listening for the ViewModel events in the
onStart
method and cancels the job of that Coroutine inonStop
.
Conclusion
This is a high level overview of how to implement a MVI project using RxJava and Coroutines. We didn’t have time to touch on some other concepts we can see in the projects but I think it’s worth mentioning:
- Zip operator implementation in the Coroutines project.
- Interop between Coroutines and RxJava in the interop project.
Thanks for reading,
Manuel Vicente Vivo