Android MVI architecture with Jetpack & Coroutines/Flow — Part 2

Pavlos-Petros Tournaris
Google Developer Experts
4 min readApr 11, 2020

Creating ViewModels in an MVI architecture

If you haven’t read the previous article of these series, you can find it here:

Part 1 — RecyclerView adapter with ViewBinding

Now that we have created a base RecyclerView adapter in order to bootstrap lists, we need to see how we will fetch , transform and present the data of our screen.

Entering ViewModel

ViewModel is the Jetpack component that we will need to extend in order to host the orchestration of several other components. The orchestration of the other components will result in fulfilling our business goals.

Usually in a ViewModel we will need to make some API calls or Database operations and accordingly display our updated information in the UI consumer of our ViewModel.

The components that we will showcase and are currently using in our example project, are UseCases aka Interactors.

We prefer to “inject” those usecases into the constructor of our ViewModel. By doing this, we gain in testability.

Constructor injection

With Koin

A ViewModel can have its dependencies injected on its constructor pretty easily if we are making use of Koin.

We declare a new Koin module like the following:

We use factory in order to always get a new instance injected in our ViewModel (depending on the needs of our app and the work done by our usecase, it might be suitable to declare it as a singleton). The rest of the dependencies of our UseCase are fetched from the rest of the graph (we will analyze that in a future article).

With Dagger

In order to inject dependencies through constructor when using Dagger, we need to create a ViewModelFactory and bind our ViewModels by their class name to the Dagger graph.

A quick implementation of that injection is the following:

User actions consumption in ViewModel

Since our desired architecture is an MVI one, we need to create a stream of user actions/intentions that our ViewModel will consume and act upon.

The approach we have used in our example project is the following:

Our ViewModel here is using a ConflatedBroadcastChannel which is converted to a Flow in our init method. On each emission we are invoking the equivalent suspend fun that will either produce some data to be propagated on our UI or do some other work!

Exposing data to our UI (lifecycle aware) components

Now that we are able to consume user actions/intentions we need to see how we will propagate data to our UI.

In order to do that we will prefer to use the database as our single source of truth and produce a stream of data that we might pre-process before delegating to our UI.

For the example we will observe a table in our local database, where we have stored all of our Github repositories (we will examing how we fetched them on a future article).

Our injected UseCase here can be observed in order to return a Flow<T> after it has been invoked in order to produce its work. The invocation of the UseCase is being done on the end of our init method.

We then map the emitted database entities into our adapter items and convert them to LiveData before we “send” them to our UI (in our case a Fragment). Last but not least, since we are using Flow, we need to use the viewModelScope.coroutineContext in order to take advantage of buil-in cancellation for our Flow.

All we need to do now is just observe this LiveData and submit the list of items to our adapter. By taking into account the fact that we diff our items properly base on the work we have done in the previous article, we can be sure that despite the number of invalidations in our DB table we will have the minimum required updates in our adapter.

Another enhancement we can do here, is to also use .distinctUntiChanged() operator in order to avoid identical database emissions.

Disclaimer: UseCase implementation is inspired by Chris Banes TiVi repo

Conclusion

This is a proposed way of how to approach an MVI architecture based on what we have seen that works for us :) We will cover the testing part of our ViewModel in one of the following articles! Any enhancements or suggestions are welcome! #staysafe

You can find all of the code above and examples of their usage to the following repo, which will be the one we will cover in this series of articles!

--

--