MVI Architecture with Kotlin Flows and Channels

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. Model — View — Intent
- 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
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 State
can 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 thisState
into smaller ones with extraStateFlows
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.