We can also think about another good idea: MVI. Here is the excellent series by Hannes Dorman http://hannesdorfmann.com/android/mosby3-mvi-1

… but sorry, too many classes for me 😱😭! I would like something that we could write in just a bunch of lines and stick to my ViewModel class. How could we do that? 🤔

ViewModel, Actions & States 🔄

Let’s formalize a bit all those things with some definitions:

a state defines a set of data, that will be exposed by the ViewModel at one time

defines a set of data, that will be exposed by the ViewModel at one time an Action is a function offered by the ViewModel, that let you create a new ViewModel state

is a function offered by the ViewModel, that let you create a new ViewModel state ViewModel exposes actions and is the only component allowed to mutate its state

The View observes a stream of states (data class, sealed class inheriting from UIState ).

Those potential states represent the contract that will be used by the View. A ViewModel exposes Actions to the view and offers results as streams of states.

Let’s take a sample app

Let’s take a use-case (some people recognize the screenshot below from my workshops). Let’s focus on the last screen: it displays the weather of one day.

Weather App — the Last screen is about to show weather for one day

Let’s write the states (our contract) that we want to expose to our View:

An init state (where we don’t have any data yet)

a “Weather” state (for our main data)

a Failed state, in case anything went wrong

Now let create a class that extends AndroidDataFlow and let’s define the getWeatherOfTheDay action to push some states:

DataFlow exposing getWeather action and setting initial state in init block

The getWeatherOfTheDay will retrieve the weather for given day (in String), and will push a Weather state update to the view.

Every state is an immutable Kotlin data (data class or object), that is emitted only by the ViewModel.

The View just calls the ViewModel actions and bind observed result for UI rendering. The Activity’s function onStates allows observing incoming states. Here we are binding directly “by hand”.

Finally, you can test your flow very easily with Mockk:

Our system has predictable data states over time! And even if you have several actions triggered together in parallel, we ensure that we have only one state at a time.

Your app is easier to debug: every state update is tracked by Uniflow. This state logging can be easily redirected to any logging system (Crashlytics…). Finally, it’s fairly easy to understand what’s going on in production and replay any buggy situation.

Uniflow allows us to make a guard for a given state: it ensures that we trigger an action only if we are in a given state, and then we have a consistent flow of states.

Sometimes, you don’t want to push only new UI updates. In that case, we will use events to trigger “side effects”. Their usage is very similar to states, check the documentation.