MVI for Compose (Part 2)
MVI in a nutshell
Here are the things which conceptually distinguish MVI for MVVM
- All commands come as intents (don’t confuse them with android intent). Intents are events which come from the user and from the system.
- Reducer. Reducer is responsible for processing all intents and modifying the state based on intent data
- MVI is defined by contract, contract includes state, fixed set of intents and single events
- The presence of state versions. When state changes, the old state is not discarded, but stored in the event chain. If you wish, you can return to the previous state without any problems. In my opinion, this feature is really optional. Within a year of using MVI, I had only one case when it was necessary to rollback to the previous state.
- Single events — optional events which are not supposed to be processed by reducer, and they act only one time.
Implementation of MVI
MVI Contract
This part is quite simple, we have to create three interfaces State, Intent и SingleEvent
Each screen’s contract will be implemented through these interfaces.
MVI processor
We are going to extend ViewModel implementation and create an abstract class which works with our contact
Don’t forget to add dependencies for ViewModel
Receive intents
Now we have to teach our MVI to receive and store intents. In order to do it you can use SharedFlow. The intents must be received in a separate coroutine so as not to block the execution flow
Handle intents
To process incoming intents when initialising the MviProcessor class, you need to subscribe to the intent flow
Screen state
To do this, you can define two fields — one will contain the initial state of the screen, and the other for the state that the intents modify.
initialState()
function will be implemented by the successors of our MVI, and each screen defines its own initial state.
During initialisation the screen will take the initial state, successors cannot change it directly, this is the task of incoming intents.
Reduce state
Operation Reduce is really simple. Intent and state are received, and the result is new state.
Change of state will be available only for our MVI using interface
Let’s combine these two operations
Now let’s add a call to this function in the processing of intents
Side effects
Any intent is able to trigger one or several side effects as Intents. For instance, changing the text in the search bar launches a request to the backend for the entered query.
Some intents do not trigger side effects and it must be considered.
The function responsible for this will be the one which receives an intent and current state of the screen and returns an optional side effect, which will also be an intent.
and if handleIntent
has return something, you have to start coroutine for new intent.
Now in order to create a screen with MVI, we have to define contract and override 3 methods of MVI class.
It’s not a full implementation just yet, additional ways for sending SingleEvent and subscribing to Flow will be described in the next part.