ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Improving UI State management by using a Finite State Machine and MVI architecture

In this story, I’ll talk about why I believe we can strongly improve the UI State management between the View and ViewModel by using a Model-View-Intent architecture with the help of a Finite State Machine. It provides more structure to the communication, plus, it improves code legibility, maintainability, and testability.

Before we start, I’ll assume the reader has basic knowledge about Model-View-Intent and also about Model-View-ViewModel.

Who’s Next?!

To explain this idea, I’ll use a personal project called Who’s Next?!, which consists of a simple timer application that lets you know when it’s time to change the goalkeeper in a football game with friends.

Who’s Next!?

Finite State Machine (FSM)

A state machine is a behaviour model. It’s called a finite state machine because it consists of a finite number of states. Based on the current state the machine relies on user input or in-state calculation to determine the next state. I prefer to use deterministic FSMs because they’re easier to reason about, the same input will always result in the same output. When implementing a FSM the syntax to keep in mind is: From a current State on a specific Event we transit to another State and that may produce a SideEffect.

In Who’s Next?!, we can receive one of the following States:

FSM states

To change between them we have the following Events which represent user actions performed when using the application:

FSM events

We can also visually describe the relationship between States and Events by a diagram:

Who’s Next FSM

Finally, our state machine can also produce SideEffects when transitioning between states:

FSM side effects

Later, I’ll use these side effects as “state transition callbacks” to trigger UI State changes but I’ll cover that in the next section.

Implementation

I’ve used Tinder’s State Machine, a Kotlin and Swift Domain Specific Language (DSL) for FSM, and thanks to it the implementation is pretty straightforward:

DSL

Let’s recall our diagram and write the FSM code to alternate between both Idle and SettingTimer states:

FROM -> TO by [EVENT]Idle -> SettingTimer by [OnSetTimer]
SettingTimer -> Idle by [OnTimerSet]
Idle and SettingTimer configuration

And the result is:

FSM operations output

Pretty simple. Now that we know how to setup a state machine and how it behaves, let’s see how can we use its side effects to emit UI State updates.

Model-View-Intent (MVI)

It’s out of scope a deep dive explanation about this architecture - there are plenty out there and very good ones -, but briefly, it provides the following:

  • Immutable model (state) to achieve the single source of truth principle
  • Unidirectional and cyclical data flow
  • Thread safe when implemented with functional reactive programming

To take advantage of this architecture I’ve used Orbit Multiplatform library because I believe it offers three no-brainers:

  • A very lightweight learning curve
  • Composition over inheritance approach (incremental adoption 🎉)
  • MVVM architecture update (with KMM support)

And thus, like the authors, I consider it to be a MVVM+.

While this library has the same notion of State and Side Effects we use them in different ways. The first represents the UI State at a given point in time, the second is used to perform one-off events like Toasts, Navigation, etc. We won’t be using the latter in this example.

In the heart of the Orbit Multiplatform system we have a Container which is responsible to retain the State and expose two channels to listen for data updates (I’ll describe them shortly). There’s also a ContainerHost interface that allow us to use MVI verbs, we call them operators.

To handle user interaction we have at our disposal three operators: intent, reduce and postSideEffects. Within the first - intent - we can invoke the other two to run business operations to transform data. With - reduce - we atomically create the new UI States by applying transformations to the current one. These changes will be sent automatically to the stateFlow observed by the View. Finally, we have the postSideEffects that sends one-off events to the sideEffectFlow (also observed by the View).

Implementation

Let’s go back to Who’s Next?! application - where we’ve implemented a FSM to alternate between Idle and SettingTimer -, to add UI State update logic.

It’s represented by:

MVI State

Before we adopt the MVI architecture our ViewModel and View looked like:

MVVM implementation

With Orbit Multiplatform we change it to:

MVI implementation

Almost done, the only piece missing is connecting with the FSM. Let’s first remember its syntax: from a current State on a specific Event we transit to another State and that may produce a SideEffect. I’ve also said that I would use these SideEffects to trigger TimerUiState changes.

Thus, our FSM+MVI becomes:

FSM (state: Idle and SettingTimer) + MVI implementation

Have you noticed the only public methods represent an intent to change the state, but they all get double checked by the FSM before using the MVI operations to do so? Pretty cool! 🍒

FSM+MVI

Conclusions

The following animation show us all Who’s Next!? States available as well as the FSM console output:

Who’s Next!?

Final thoughts

I found it very efficient the use of both this two architectures, even if by that it means we need to add some extra lines of code for the State Machine. It will pay off in terms of legibility, testability and robustness.

The FSM helps us fully specify and validate all the states and transitions available. We can create it even before having UI or business logic.

Regarding Orbit Multiplatform system, we have a very clean and simple MVI (MVVM+) architecture out of the box. It abstract us from the complexity that others libraries don’t, and the fact that it’s built upon a composition methodology it makes it possible to adopt/migrate at our own pace. Fun fact: you don’t even have to use it with ViewModels if you want to.

Also, I believe the single source of truth principle is doubly assured, because with MVI we get that by default, but with FSM we make sure that the only public methods from the ViewModel are the ones that will trigger known State transitions which will produce SideEffects to change atomically the UI State. The FSM acts as an extra layer of security.

Next steps

Wouldn’t it be nice if we could share this architecture between mobile platforms? Go no further:

As always, I hope you find this article useful, thanks for reading and Matthew Dolan for his review.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Responses (1)

Write a response