The Contract of the Model-View-Intent Architecture

Benoît Quenaudon
ProAndroidDev
Published in
4 min readNov 16, 2017

There are multiple articles, talks, and podcasts that address the topic of what the Model-View-Intent architecture is, but I rarely hear about what I think are the principles of this architecture.

Model-View-Intent in a few words

The MVI architecture is a pattern which aims at organizing the higher layers, i.e. close to the UI, of your application to make its development simple. Now, the architecture is reactive and since most of us, Android developers are not used to the reactive paradigm, I believe the simplicity of the MVI architecture remains overlooked.

The Model-View-Intent Architecture as I see it

As I see it, the simplicity is found in a contract which the MVI architecture defines that the developer needs to respect. I’d like to share what this contract is.

User Interface

The UI (or the View) is separated into two distinct components: the listening and the rendering. The job of the listening part is to listen to the user and emit any user’s originated intent (e.g., click, scroll, etc.) for the ViewModel to listen. The job of the rendering part is to render a state onto the screen (e.g., display lists, switch loading/empty views, etc.). Here is the UI’s contract:

The “listening” and the “rendering” cannot talk to each other

In Android, inputs and outputs are at the same place. This is true for both the hardware (the screen) and the software (activity/fragment). I think it helps one’s mind to keep them both isolated from each other. The listening only listens to the user. The rendering only listens to emitted states from the ViewModel.

The “listening” cannot write anything to the UI

It can only read it. If the user clicks on a refresh button, the code should not display a loading view. It only needs to emit an intent which conveys that the refresh button has been clicked. That’s it. If extra information is needed, the listening may read the UI (e.g. which row has been clicked, what is the value of the input, etc.) but should never change anything.

The “rendering” cannot read anything from the UI

It can only write it. It means that the state which is going to be rendered, is self-sufficient. It should contain every data required to render the screen.

One Entry Point, One Exit Point

From the point of view of a MVI architecture, the UI has two, and only two methods:

interface MviView {
Observable<MviIntent> intents();

void render(MviViewState state);
}

It emits intents for the ViewModel to listen to, this it the job of the listening. It also takes a state to render a screen, this is the job of the rendering. The UI has only one entry point, and it has only one exit point.

ViewModel

UI Independent Logic

In Android, because of configuration changes, the UI may have a shorter lifecycle than the ViewModel. The ViewModel should never be affected by the lifecycle of the UI. Even if the UI disconnects or is discarded, the ViewModel should keep its data flow alive in order to keep ongoing processes and the latest cached state safe.

One Entry Point, One Exit Point

From the point of view of a MVI architecture, the ViewModel has two, and only two methods:

interface MviViewModel {
void processIntents(Observable<MviIntent> intents);

Observable<MviViewState> states();
}

It takes an observable of intents in order to execute their corresponding business logic. This will produce a result that will be reduced with the latest cached state to create a new state that the ViewModel will emit in the data flow. The ViewModel has only one entry point, and it has only one exit point.

user(rendering(businessLogic(listening(user()))))

This is the reactiveness of the architecture and the contract between its components. This is a cycle where each component listen to the previous one, and is listened by the next. One component processes its input in some way and its output becomes the input of the next component, ad eternum.

I changed the naming because Model-View-Intent conflicts head first with the Android framework.

  • Listening is the Intent in MVI
  • BusinessLogic is the Model in MVI
  • Rendering is the View in MVI
  • User is our best friend.

Unidirectional Data Flow

The data can only be passed around via the data flow and this data flow can only go one way. Each components of the architecture only knows what kind of data they are listening to and what they’re going to do with it. The only one way for the components to communicate to each other is by passing an object onto the data flow.

Immutability

All objects passed onto the data flow are immutable, they never change. Components have to create new objects to pass new or updated data.

Side Effects

Side effects are necessary for any application to be useful. The MVI architecture isn’t an exception. But the contract sets limitation to where side effects may happen. The goal is to restrain as much as possible the scope of those side effects.

Side Effects’s Three Zones
  • Inside the BusinessLogic: it may need to communicate with the repository to store/load things, etc.
  • Inside the Rendering: it may write the UI.
  • Inside the Listening: it may read the UI.

Side effects can only happen in this 3 cases. Also important, they cannot communicate to each other. Every side effect is contained inside its “zone”.

Conclusion

The Model-View-Intent architecture is not easy to implement but I believe that if it is implemented in respect to the contract it brings, it can help make the development and maintenance of an application simple and robust. What do you think?

ps: if you’re curious about what an implementation of the MVI architecture could look like, you can check this sample project.

Responses (11)

What are your thoughts?