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.

App screenshots

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:

  1. Pure Coroutines.
  2. Pure RxJava.
  3. 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:

The app can restore state after a configuration change

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.

App MVI Architecture

As you can see, two communications take place here:

  1. View -> ViewModel with the UserAction. The ViewModel needs to listen for View events and be able to react to them.
  2. 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 Architecture communication points

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.

We can use Subjects for the View <-> ViewModel communication

The ViewModel can have a userActionSubject and a viewStateSubject.

  1. The View calls onNext on the userActionSubject to send events to the ViewModel. The ViewModel subscribes to it when it initializes. This subject can be of type PublishSubject.
  2. The ViewModel calls onNext on the userStateSubject to send events to the View. The View subscribes to it every time it’s visible to the user (we can register in onStart). This subject should be of type BehaviorSubject. We need a BehaviorSubject 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.

RxJava ViewModel part of the implementation
RxJava View part of the implementation

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.

  1. 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 Jake Wharton’s RxRelay library and use an PublishRelay.
  2. 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.
Better implementations of the RxJava MVI Architecture

Watch out for…

  • The ViewModel subscribes to the userActionSubject on Schedulers.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 on AndroidSchedulers.mainThread().
  • The View starts listening to the viewStateSubject in onStart and disposes the subscription in onStop.

MVI in Coroutines

We need to achieve the same behavior we programmed in RxJava with Coroutines now.

We can use Actors and Channels for the View <-> ViewModel communication

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.

  1. 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.
  2. For the ViewModel -> View communication, the ViewModel creates a ConflatedBroadcastChannel. If we used the BehaviorSubject to reply events when a new subscription comes in in RxJava, we used the ConflatedBroadcastChannel 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 the userActionActor in the onCleared ViewModel method.
  • The View creates a new Coroutine using launch(parentJob + CommonPool) to start listening to the viewStateChannel with the consumeEach extension function. In that code, we call withContext(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 in onStop.
Coroutines Actor implementation
Coroutines listenViewModel View implementation

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