Android MVI (Model-View-Intent) Architecture — Example code
As developers you may have heared about the terms like MVC,MVP and MVVM which are frequently discussed architectural patterns in android development. MVI is also an architectural pattern which introduced recently to Android with the introduction of Kotlin Coroutines and Flows. MVI stands for Model-View-Intent but this word “intent” doesn’t refers to the Android Intents that we know.
Let’s breakdown the main 3 layers.
Model- Other than representing the data and the business logic of the application model maintains a state which can be changed through actions of user.
View- Represents the UI layer of the application which consists of Activities and Fragments.It accepts different model states and display it as a UI.
Intent- Again, do not confuse it with the Android Intent, it’s the intention to perform an action either by the user or the app itself.
MVI Workflow
As you can see in the diagram the flow goes like this,
User interaction happen which will be an Intent → Intent is a state which is an input to model → Model stores state and send the requested state to the View → View Loads the state from Model → Updated UI is displayed to the user.
This Architecture,
- Is unidirectional
- Maintains state
- Has better separation of concerns(therefore easy to maintain)
Let’s do some coding and try to understand better.
Create a project and add following dependencies
Create the folder structure as follows.
What we are going to do is, make an REST api call and show it’s response data.I will be using https://reqres.in/api/users sample api(GET) and it’s response is as follows.
{"page":1,"per_page":6,"total":12,"total_pages":2,"data":[{"id":1,"email":"george.bluth@reqres.in","first_name":"George","last_name":"Bluth","avatar":"https://reqres.in/img/faces/1-image.jpg"},{"id":2,"email":"janet.weaver@reqres.in","first_name":"Janet","last_name":"Weaver","avatar":"https://reqres.in/img/faces/2-image.jpg"},{"id":3,"email":"emma.wong@reqres.in","first_name":"Emma","last_name":"Wong","avatar":"https://reqres.in/img/faces/3-image.jpg"},{"id":4,"email":"eve.holt@reqres.in","first_name":"Eve","last_name":"Holt","avatar":"https://reqres.in/img/faces/4-image.jpg"},{"id":5,"email":"charles.morris@reqres.in","first_name":"Charles","last_name":"Morris","avatar":"https://reqres.in/img/faces/5-image.jpg"},{"id":6,"email":"tracey.ramos@reqres.in","first_name":"Tracey","last_name":"Ramos","avatar":"https://reqres.in/img/faces/6-image.jpg"}],"support":{"url":"https://reqres.in/#support-heading","text":"To keep ReqRes free, contributions towards server costs are appreciated!"}}
To map the above json response, create following classes inside model folder.
Create following interfaces and classes to make api call using retrofit. We use retrofit library to make REST api calls in this project.
We use GsonConverterFactory to map the response json to out Data.kt class .
Create MainRepository class inside Repository folder and add the api call there.Then we can call the repository method, getUsers() from our ViewModel class.
Now create the DataState.kt under viewstate folder. In this class, we are defining the states Inactive, Loading, ResponseData, Error. Each state can be loaded on to the view by the data intents.
Then create DataViewModel class inside viewmodel folder.
Here in the ViewModel, we are observing on the dataIntent to perform the action on it.When user clicks a button from UI, the action comes to viewmodel and call fetchData() since we listen to incoming intents here.Then inside fetchData() first we change the state to Loading and make the api call.Once response is came change the state to ResponseData with our data received.If any error occurred can change the state to Error.
All above states are being observed in the MainActivity.
We cannot create ViewModel on our own. We need ViewModelProviders utility provided by Android to create ViewModels.Because Android will only give you a new instance if it’s not yet created for that specific given ViewModelStoreOwner.
Let’s set up ViewModelFactory under the util folder.We are instantiating our viewModel in this class and we will return the instance of the ViewModel.
I use Kotlin-Coroutines and Flow API in this project. Coroutines is the Kotlin’s way of handling asynchronous tasks and Flows are quite similarly work as RxAndroid observer pattern.At the beginning developers used LiveData /RxAndroid(came in handy with new other operations like esay threading) with design patterns like MVVM. But with the introduction of Kotlin coroutines, Flow api was also introduced which also have the capabilities like doing asnchronous work in separate threads which RxAndroid had.
Let’s create the MainActivity inside view folder and listen incoming data,trigger UI actions and update the UI.
In line 18 we send the intent to ViewModel on user button click to get data. From line 49 to 69 we listen to state changes and update UI depending on each state. We will be showing users list in a recyclerview.
lifecycleScope
is a scope that is managed by a SupervisorJob
whose lifecycle is tied to the Fragment/Activity
's lifecycle. So just by using lifecycleScope
your coroutines will be cancelled when the underlying Lifecycle
instance is destroyed. Simply, we use lifecycleScope
to make coroutines, lifecycle aware. If you have used RxAndroid you know that it is not lifecycle aware and we have to dispose the disposables inside onDestroy method.
Create the DataIntent class inside the intent folder.
Finally create the recyclerview adapter, MainActivity xml and recyclerview row xml as follows respectively.
Here I have used Glide to load images from urls.
Once you click on SHOW USERS button shown below in the UI,
the users will be shown in a list as follows.
Hope you learned something. Final code be can found here.
Hit on clap if this helped you and follow me for more articles.