Uni-Directional Data Flow (MVI) in Kotlin Multi Platform World.

Rohit Singh
3 min readJun 19, 2020

--

MVI stands for Model-View-Intent.

Recently this has been a hot topic in our developer community and there has been lot of libraries that solve this problem, but it becomes too overwhelming to understand the actual inner working behind it.

If you are new to MVI I’d suggest you to read a thorough series of post http://hannesdorfmann.com/android/mosby3-mvi-1

In this post we would go through how we can setup our own uni-directional data flow architecture using coroutines & flow in kotlin multi-platform.

To build this MVI flow we would be looking at following interface

Action : It is mapped as user input in the screen like click or swipe.

Result: The actions or the user input are passed to viewmodel. It filters the actions by it type and process it. Every action generates a Result. A new State is created by copying the current State and changing the necessary bits according to the Result.

State: It is the View state of the screen. Whatever the View shows on screen is represented by a state.

This interface are than subclass by sealed classes, except for state that is a data class. So using sealed class this makes sure every action is mapped to a result.

This modeling incorporates every actions or input the user can trigger and what will be the resultant change in the screen.

As, we see the architecture really forces us to model it in a particular structure. Every actions should be listened by Viewmodel.

For actions we would be using Channels.

It is is how we can transfer streams of values in Coroutines. It is similar to RxJava subject or MutableLiveData.

private val actions: Channel<A> = Channel(Channel.UNLIMITED)

We would be using offer(element: E) method to send items in channel.

This action is listened by viewmodel, actions are processed to generate Result.

To follow the reactive approach the actionToResults method should return a stream. Flow is similar like Observables in RxJava or LiveData.

abstract suspend fun actionToResults(action: A): Flow<R>

This method is responsible to filter the actions and generate the result and pass it in the stream

Suspending functions aren’t compiled to ObjC so we can’t use those on iOS but thanks to CFlow from KotlinConf app, we are able to do so. See this for the source code they have solved this use case in their app. This is one of the way of solving this.

Now we have actions and results. Let us discuss about State now.

ViewModel should have a way to communicate to view when there is change in state. So to solve this we would be using, Channels again but ConflatedBroadcastChannel.

ConflatedBroadcastChannel : Whenever Back-to-back states elements are send— only the the most recently sent value is received, while previously sent elements are lost.

For coroutines in multiplatform world to work in Main thread, we would be using expect/actual

The expect keyword declares that the common code can expect different actual implementations for each platform. We can use this for things which are platform specific.

Android actual implementation

iOS actual implementation

Below is the implementation of ViewModel. We can see here we pass initial state and CoroutineDispatcher that we discussed above as constructor parameter.

In above code we are using something called scan. Scan operator takes the current state and result to combine it into a new state.

We need to call onCleared() function from the view, whenever the view is getting destroyed.

If you want to check the sample example. You can check here:

Conclusion

Hopefully, you would have a better understanding of implementation of MVI or uni-directional data flow and how to get started. There can be multiple ways to approach this choose whatever works for your project.

Please feel free to ask any questions or views about modularization, do reach out at LinkedIn or Twitter.

If you like the article, remember to share it with the android community. Happy Coding!

Resources

--

--