All You Need for MVI is Kotlin. How to Reduce Without a Reducer?

In this article, I describe my attempt to implement a simple state reducer based on Kotlin Flow.

Maciej Sady
Better Programming

--

Motivation and Context

Like any Android developer following the latest trends, I like MVI architecture and the unidirectional data flow concept. It solves many issues out of the box making our code even more bulletproof.

In this article, I won’t go into detail about what MVI is, but you can find many great write-ups about it. Like:

Playing with libraries like MVICore, Mobius, or Orbit inspired me to experiment and try to implement a flow that can perform state reduction.

That’s how StateReducerFlow was born. Let me explain how I’ve built it, how it works, and how you can use it.

Thinking Process

Please keep in mind that the following examples are simplified.

Let’s start with a simple counter. It has one state that can be changed with two events: decrement and increment.

Using the above approach, we can structure our logic in the following way:

Event -> ViewModel -> State

One issue, though, is that handleEvent can be called from any thread. Having unstructured state updates can lead to tricky bugs and race conditions. Luckily, state.update() is already thread-safe, but still, any other logic can be affected.

To solve that we can introduce a channel that will allow us to process events sequentially, no matter from which thread they come.

Much better. Now we process all events sequentially but state updates are still possible outside of the updateState method.
Ideally, state updates should be only allowed during event processing.

To achieve that we can implement a simple reducer using runningFold.

Now only the reduceState method can perform state transformations.

When you look at this example ViewModel you may notice that only the reduceState method contains important logic. Everything else is just boilerplate that needs to be repeated for every new ViewModel.

As we all like to stay DRY, I needed to find a way to extract the generic logic from the ViewModel.

That’s how StateReducerFlow was born.

StateReducerFlow

I wanted StateReducerFlow to be a StateFlow that can handle generic events. I started with this definition:

Moving forward I extracted my ViewModel logic to the new flow implementation:

As you can see, the only new things are a few overrides from StateFlow.
To construct the flow you provide the initial state, the function that can reduce it, and the coroutine scope in which the state can be shared.

The last missing part is a factory function that can create our new flow. I’ve decided to go with ViewModel extension to access viewModelScope.

Now we can migrate our ViewModel to the new StateReducerFlow.

Voilà! The boilerplate is gone.

Now anyone who has access to StateReducerFlow can send events to it, e.g.

That’s it! Are you interested in how it works in a real app or how it can be tested? See my example project:

https://github.com/linean/StateReducerFlow

Stay inspired!

--

--

Programming enthusiast with a technical background, constantly looking for new learning opportunities.