MVI for Compose (Part 3)

Semyon Zadoroznyi
3 min readAug 26, 2022

--

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

<- Previous part | Next part ->

--

--

Responses (1)