MVI Architecture with Kotlin Flows and Channels

Yusuf Ceylan
ProAndroidDev
Published in
4 min readJan 11, 2021

--

MVVM is the recommended architecture and many developers use it. But just like other things, architecture patterns are also evolving.

MVI is the last member of MVx family. It has a lot in common with MVVM but has more structured way of state management.

MVI consist of three parts. ModelViewIntent

  • Model represents the state of the UI. for example UI might have different states like Idle, Loading or Loaded.
  • View basically sets immutable states that comes from ViewModel and update UI.
  • Intent is not the traditional Android Intent. It represents user’s intent when made an interaction with UI. Just like clicking a button.

Now let’s move to code

First we need to create some interfaces that describe the type of classes

  • UiState is current state of views.
  • UiEvent is the user actions.
  • UiEffect is the side effects like error messages which we want to show only once.

Now create a base class for our ViewModels

What is the difference between StateFlow — SharedFlow — Channel?

With the shared flow, events are broadcast to an unknown number (zero or more) of subscribers. In the absence of a subscriber, any posted event is immediately dropped. It is a design pattern to use for events that must be processed immediately or not at all.

With the channel, each event is delivered to a single subscriber. An attempt to post an event without subscribers will suspend as soon as the channel buffer becomes full, waiting for a subscriber to appear. Posted events are never dropped by default.

You must read this great article by Roman Elizarov.

For handling UiState we use StateFlow. StateFlow is just like LiveData but have initial value. So we have always a state. It is also a kind of SharedFlow. We always want to receive last view state when UI become visible.

For handling UiEvent we use SharedFlow. We want to drop event if there is not any subscriber.

Last, for handling UiEffect we use Channels. Because Channels are hot and we do not need to show side effect again when orientation changed or UI become visible again. Simply we want to replicate SingleLiveEvent behavior.

Then we need setter methods for UiState , UiEvent and UiEffect

In order to handle Events we have to collect event Flow in init block of ViewModel

We completed BaseViewModel implementation so let’s move to MainContract which is a contract between MainActivity and MainViewModel .

We have only two event. OnRandomNumberClicked event will be fired when user clicks to generate random number button. Also there is a simple toast button that shows a Toast message and simulate SingleLiveEvent behavior.

RandomNumberState is a sealed class that holds different states of random number like Idle, Loading and Success Random generator method that we will write later is a simulation of network call.

State is a simple data class that corresponds to state of UI elements.

Effect is the simple action that we want to show only once based on Event result.

You do not have to use sealed classes in the view state, you can just use simple variables like below.

Now that we completed MainContract we can move on to MainViewModel that handles the actual logic.

We have to create initial state of views. In our use case, it is an empty state.

We handle each event in the handleEvent method. Whenever we add an event to our contract we also have to add it here. So all events can be managed from the same place.

We call generateRandomNumber method every time OnRandomNumberClicked event triggered.

Starts with Loading state and then based on the result we change state to Success or Idle

Based on your use case, you want to create an Error state and set it when number is even.

If an error occurs we set an effect and show a toast message.

As the last step, we need to show view state at the UI.

Every time a button clicked, we will fire corresponding event.

For update UI we have to collect uiState and refresh views with this state data.

For replicate LiveData behavior, we used launchWhenStarted By this way, flow will be collected when lifecycle at least STARTED state.

Here is the result of this simple app

Result

Let’s summarize general app flow. It is pretty simple.

First we fire an Event which is related to user action like button click. Then as a result of this Event, we set a new immutable State. This Statecan be Idle, Loading or Success.Since we use StateFlow , as soon as new State comes UI is updated.

If there is an error and need to show one time message like toast or alert dialog, we set a new Effect

Pros

  • State objects are immutable so it is thread safe.
  • All actions like state, event, effect is in same file so it is easy to understand what happens in the screen at one look.
  • Maintaining state is easy.
  • Since data flow is unidirectional, tracking is easy.

Cons

  • It causes a lot of boilerplate.
  • High memory management because we have to create lots of object.
  • Sometimes, we have many views and complicated logics, in this kind of situation State become huge and we might want split this State into smaller ones with extra StateFlows instead of just using one.

MVI is the last member of MVx family. It has a lot in common with MVVM but has more structured way of state management. In this article we have covered MVI pattern and made a simple implementation. You can download the full source code here.

Thanks for reading! Please clap if you like it!

Thanks to Matthew Dolan, I learn that there is an internal library which works very similar way with the article. You can check the framework from the link below and also read his comment from here.

--

--