MVI for Compose (Part 3)
- Problems and benefits of MVVM
- Custom MVI
- Practical examples, continued implementation (You are here)
- Automating/reducing boilerplate
- Best practices and conclusions
In this chapter we are going to have a look at 2 simple examples and find out what else is needed for our implementation
Example 1: simple data loading
Let’s say that after starting the app we see a screen with a button, and after you click on it, a loading indicator appears, and after the loading is over, there is a list.
What the state of such a screen is going to look like
Everything is quite simple with intents. We have two intents, the first one triggers the loading, the second one comes from the system when friends are loaded
As we don’t have single events yet, we’ll leave them empty
Now in MVI of our screen we implement MVI with our contract
The situation with initial state is simple. We can see the button, empty list and there is no loader, this is regulated by default.
Let’s process intents for our UI
Delay function simulates network delay, getFriendsUseCase() is a domain layer UseCase to load the list of friends. FriendListIntent.ShowFriends(friends) is a desired side effect which is the system supposed to start after completing the task
This is what reducer will look like for our intents
Now we just have to connect it all to our UI layer and look at the result. The project itself and examples are public on GitHub.
State as sealed class
Nothing stops us from making our state a set of classes
In this case initial state is
As for reducer, instead of copying it will create and give away Loading and FriendList classes.
It is important to remember that as functionality of such state grows, it will get more difficult to transfer state data between these screens. This approach is helpful when your UI has two or more independent screen states.
Example 2: Observe streams of data
We have worked out how intents start a task and return the result. but what if we need to subscribe to some data source (for example, a database or a timer) and update UI with every new information emit
Extending functionality of MVI class
In order to do it, you have to add a function which takes suspend function and keeps its Job object to control the set of executed tasks for specific intents.
Here is its short version
You also can stop any task using taskId
you provided for start
Full implementation you can see here
Let’s implement a small timer, here is what its contract will look like
Now you can start initial state for out MVI
Reducer will be extremely simple
Now let’s move on to processing side effects
When timer start, we can use our new function observeFlow
UpdateTimer affects only UI and doesn’t start side effects
is TimerIntent.UpdateTimer -> null
TimeIsUp is not supposed to start side effects but has to notify the user that the count down has ended. To do it we have to start SingleEvent, and let’s add the function for this to MVI class
Now our intent can trigger it
And that’s all. What is left is to subscribe to all these events with UI and logic of the application will work, the example with subscription you can see here
TL;DR
I tested the described approach in a production application with a large number of users and on screens with different degrees of complexity. I must notice, that this approach a certain number of problems and there are some ‘best practices’ which should be followed and can be automated to make it possible for several people to work comfortably with one code base.
These methods will be described in the next part