software engineering Aug 7, 2018

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 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).

MathCoroutines - Example of Coroutines with a Math example

MathRxJava - Example of RxJava with a Math example

MathRxCoroutines - Example of integrating RxJava and Coroutines with a Math example

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:

Mobile screenshots with one rotated horizontally 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.

Illustration of App MVI Architecture 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.

Illustration of MVI Architecture communication points 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.

Illustration of View <-> ViewModel communication 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.

Code example of RxJava ViewModel part of the implementation RxJava ViewModel part of the implementation
Code example of RxJava View 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.
Illustration of Better implementations of the RxJava MVI Architecture 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.

Illustration of using Actors and Channels for the View <-> ViewModel communication 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`.
Code example of Coroutines Actor implementation Coroutines Actor implementation
Code example of Coroutines listenViewModel View 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

Manuel Vicente Vivo
Android Software Engineer at Capital One
@manuelvicnt

DISCLOSURE STATEMENT: These opinions are those of the author. Unless noted otherwise in this post, Capital One is not affiliated with, nor is it endorsed by, any of the companies mentioned. All trademarks and other intellectual property used or displayed are the ownership of their respective owners. This article is © 2018 Capital One.