MVI Architecture with Android

Rim Gazzah
Nov 2, 2020 · 6 min read
Image for post
Image for post
Photo by Petri Heiskanen on Unsplash

The application lifespan is tied to its flexibility to scale for that it needs a solid base that’s why for every project the most important step is to create the app architecture, after a good long discussion with the technical team about defining the elements included in the system, the functionality of each element and how they will be communicating with each other, we have to put a clear design of the overall architecture.

There are different architectures for android applications, from what I’ve experienced the last years the MVVM and the MVI architecture are the most common architectures used for large scale applications, even for each of them there is no one way to be implemented and it depends on the application needs, also the developers' styles that worked on it, cause I believe that independently of the Android framework every developer has their unique experience in software development and what they bring to the table is not only their knowledge but also their special way of thinking, problem-solving and designing code.

Why MVI?

The business logic and the UI rendering get tangled as a result of the state crises, how that happens? well:

  • The Presenter/ViewModel has his own state
  • The business logic produces its own state
  • Trying to Synchronising both of the above states
  • If there are no clear management of the inputs to the Presenter/ViewModel -> results of tangles processed outputs-> messy business logic and View rendering -> code smells like the Fragment/Activity become a black hole class (by attracting more responsibilities) or having divergent classes ( when you want to make an update but you have to change a class in many different ways for many different reasons) and that results from a poor separate of concern.

What’s the benefit of adapting the MVI architecture pattern :

  • State management using immutability to have a single source of truth.
  • Unidirectional data flow
  • reproducible state -> simple code reuse
  • better separation of concern -> maintainability

Breaking down the layers

  • Model: instead of having a separate state for the View, ViewModel, and the Data layer, the Model will be the single source of truth of the overall state, and it’s immutable to ensure that it will be updated from only one place.
  • View: represent the UI layer, the activity, or the fragment, it defines the user actions and renders the state emitted by the model.
  • Intent: do not confuse it with the Android Intent, it’s the intention to perform an action either by the user or the app itself.
Image for post
Image for post
http://hannesdorfmann.com/android/mosby3-mvi-2

Relying on functional programming each layer get input and feeds the next layer with its output, mathematically we expect the function f(x) to have the same resulted output for the same x input, applying this logic using immutable data and represent it with sealed classes in Kotlin( yes sealed classes will become your best friend in MVI architecture)

So let’s have a closer look at each layer and talk in more details about their interaction with each other:

Model

In MVI pattern, the model is also the data but represented as an immutable state, it means that the state will be updated in only one place in the app, which is the business logic, and that will ensure that the state won’t be changed in no other place in the app, therefore the business logic is the single source of truth that create the immutable Model.

That’s how the state will be represented, a sealed class that holds the data:

Now we have a unique state, we don’t have to manage the loading, success, or exceptions separately in different calls or operations, with a render method in the view that observes the state changes and updates the UI or executes a logic according to it.

I did an abstract definition of the view, this how I applied my logic:

Using reactive programming observing the state returned by the ViewModel and applying the new state within the render() function that will be implemented in the BaseActivity child ( you can do the same with a BaseFragment)

PS: especially with a shared ViewModel between different Fragments it’s better to use state management to not end up with messy “if-else” statements trying to manage the logic for each state of each fragment in the ViewModel and the Views 😨

Implementing state logic is cleaner, centralized, and more structured, besides it’s like you declare all your use cases in the ViewState and define them in the View.

View and Intent

Starting by creating the View ( layout file and the Fragment /Activity), then defining all the operation on the View either click action or other action generated by the app itself that’s called the Intent, to put it more simply the intention to do something.

For instance: the user types a word in the search bar then click on the button search, the click action will send to the ViewModel the Intent of SearchChatacter with the name as the text entered by the user

when an Intent is stimulated, it’ll be dispatched to the ViewModel where it will be interpreted to the corresponding Action, here you can ask why we have to interpret the Intent? can’t we simply work with it without using Actions? well, that’s the trick you can have different Intents for the same Action as you can see in the example below, furthermore, the Intents can be emitted by different Views because you can have multiple Fragment using the same ViewModel. So the ViewModel maps the Intent to the appropriate Action.

Then the Action is handled by the ViewModel, based on the result that is passed to the Reducer to define the new State that will be sent to the View.

For me, I implemented the reducer as an extension function that will be applied to the result returned by the business logic; the result will be mapped to its appropriate State by the reducer, then the View that observes the sate will eventually be updated according to it.

And finally we sum-up with this diagram that gives a clear overview of the circular and unidirectional data flow in MVI architecture:

Image for post
Image for post
MVI data flow

Wrapping up

You can find the complete sample code about MVI clean architecture in my Github repo using Dagger2, Coroutines, and Android Jetpack components:

Useful references

The Startup

Get smarter at building your thing. Join The Startup’s +776K followers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store