MVI stands for Model - View - Intent. It is a relatively new but increasingly popular approach to the whole Android Ecosystem. This shares ancestry with MVC just like MVP and MVVM
But MVP and MVVM are both a little more like MVC... MVP has Presenter class, MVVM has a
ViewModel class. Each of these classes take on similar responsibility like the Controller
of MVC, the only difference is in the way in which Presenter/ViewModel communicate to
the other layers of the architecture. In this Repo I had sketched out GitHubJobs app with MVI, all
the clean architecture layers are the same like we have in our previous repo, please refer
to Rx-Clean
to get a better understanding
of the GithubJobsApp
.
- Introduction
- Dependencies
- The Concept
- Insights on Sealed classes
- How does Rx fit in MVI
- MVI base interfaces
- Core Architecture Classes
MVI architecture introduces some new ideas that are not present on the other architectures that we use now-a-days.
- Unidirectional Data Flow.
- Immutable State ( this is a lifesaver in most of the cases).
- User is Everywhere(user as a function :)).
Moving forward, we can see that the MVI architecture is a bit more involved than the other architectures, But its unique nature helps us to simplify the flow of information and events in the APP. In other words, it can lead to an architecture that's more comprehensible, maintainable and bugFree :) approach.
MVI provides a systematic approach to adding features into our apps. Due to this benefits, many Android Dev teams are now choosing MVI over MVP and MVVM. Buckle up! lets dive in...
Many of the advancements made in the software development in the past few years have come from the
web development world. Examples include tools and patterns
like React
and Flux
. These new tools tend to favour a
functional programming approach to software development and rethought the way in which you manage
the immutable state. They also use an event driven design, which fits in quite nicely with the UX of
mobile apps. MVI was created in the webWorld and is closely associated with the javascript framework
named Cycle.js
created
by Andre Staltz
One of the key insights within Cycle.js and the MVI pattern is adding the user into the structure of the software. Much of the code we write in web and mobile are heavily user focused, as opposed to being processing-intensive. The code presents and interface that both accepts user inputs and shows resulting output. But the place the user in architectures like MVP and MVVM are not entirely transparent. In many ways, the user is kind of a view in those patterns. But in MVI, the user takes a central role in terms of expressing their intents.
In Android, the term Intent has a very specific meaning being a class in an android SDK. Intents are what we, as Android developers, to bond different components in the Android system. We use them to start activities and services, pass data to components like broadcast receivers. All Android developers are well-versed in the use of the intent class for these and other purposes. However, the world Intent in MVI is totally unrelated to the use of the class named Intent in in Android SDK.Intent in MVI is more closely thought of as Intention(the intention of a user when interacting with our app in some way).
Here is the high level flow of interaction between a user and typical modern software such a mobile app. The app Starts up and the user begins interacting with it. We call the user interactions Intents as in, the user intents for something to occur based on how they interact with the app. For example, they might enter text or taps a button. These user intents are inputs into our software, our software does something interesting with the intents in some type of model of our data and then produces the output which is relayed back to the user. This flow of interaction can be thought of as an event stream between the user and our software, with intents as input and turned into results as outputs.
We interpret the user inputs as Actions that must be taken within our app. We translate intents into actions to take on content and data within our app that us to our data repository which may be network based or stored locally or both.
We use processors to perform actions on our data. The processors produce the results. And so, at this point, the user Intents has been turned into an Action that has produced Results. Now what's left is to get those results to the user. This is where the Idea of State arises.
State is the current status of all the information shown to the user. At a very high level in the Android App, Example of the state includes Loading State, Empty State and Data State. As the events occur in the app the state is continuously updated based on the results of the events that have occurred. However, rather than maintaining state immutable properties of objects in our system, in MVI a new state is calculated for each event that occurs. The new state is based on both the previous state and the results of the actions that have been taken by the user in interacting with the software. This process of combining results with the previous state is called reduction in MVI. The new state is passed along to the view of the app which renders the state for the display to the user. In this way state is completely immutable. ie. It is never updated, but instead, it is just recalculated and passed along to the view for rendering.
" ONE OF THE MAJOR SOURCES OF THE BUGS IN ANY KIND OF MOBILE DEVELOPMENT IS MUTABLE STATE " - Joseph James (that's me :) )
So, by using Immutable state in this way in MVI, you have eliminated the source of many possible bugs from your app development projects.
Looking at the whole picture, you can see that there is a unidirectional flow of data/events that occur.
User Events -> Actions -> Processors -> Results -> New State -> User Events -> Actions and the loop continues.
- View wil have INTENTS reception and RENDER logic
- ViewModel will have :
- Intent - Action Conversion
- Action - Result Conversion
- Result - State Reduction.
You can think of sealed classes as an advanced form of Enum, This let you define a limited hierarchy of classes that can be used in kotlin when expressions in a similar way to enums. Unlike enums, you can create multiple instances of sealed class sub type.
We had discussed the theory behind MVI, from that you can imagine how streams of RxJava observables fit naturally onto the unidirectional flow of MVI.
- User Intents gets packaged into Observable Streams
- This flow into actions
- interacts with repo
- then flows into Result
- then reduced to State.
You can think of the unidirectional flow as a stream of observables going round and round as the user interacts with you app ( moment of truth guys :) ).
Now we are all set to start.
The flow of events around the MVI cycle consists of intents, actions, results and state. We want to be able to send these type of entities around using Rx Observables and will create base interfaces to represent each type.
The specific items will implement the corresponding interfaces in order to be tagged as that type.
For instance, We will use MVIIntent
Interface in the above diagram to represent all user intents.
We also need the ViewModel to process the intents and provide states that the views can observe, the View needs to provide intents to the ViewModel and be able to render new states. from the viewModel. So, lets also create interfaces for ViewModel and Views.
Please refer to this classes for more understanding.
GitHubLoadJobsIntent
GithubLoadJobsAction
GitHubJobsProcessorHolder
GitHubJobsResult
GitHubJobsViewState
JobsViewModel
JobsActivity
Feel free to raise a PR and link in an issue if you think I had went wrong in any parts of the MVI(all suggestions accepted).