Introduction
I have been living under rock for some time since the Jetpack Compose has been out in release in July 2021. I have sticked myself to XMLs and View/Data Bindings. Under same time, I started exploring MVI Design Pattern and find it fascinating as it has clear state management of UI.
Fast forward present day, I finally got the zest to try out Jetpack Compose and ride aboard the hype-train.
Let's start with an example:
Resulting the UI to be rendered like this:
I know, DaVinci would rip his Mona Lisa off for my design ¬‿¬
Anyway...
Tour de Design Pattern
I came across a great implementation of MVI Design Pattern coupled with Redux by Adam McNeilly.
A sample app showing how to build an app using the MVI architecture pattern.
MVI Example
This application was streamed live on Twitch to demonstrate how to build an application using MVI.
You can find the VOD here for now: https://www.twitch.tv/videos/1036306656
And on YouTube: https://www.youtube.com/watch?v=wTJX_lWdh60
Much of the codebase is documented, but you can expect a blog post coming soon as well.
MVI Diagram
During the stream we created a diagram to understand the flow of data in an MVI application, which you can find here. This may be helpful to review before looking into the codebase:
To summarise the content of Repository, here are the core concepts:
A State
is a representation of UI.
An Action
is an intent taken by User from the UI.
A Reducer
takes an Action
and transform the current State
to a new State
.
While Reducer
is great at handling the state of UI, it is useless when concerned with executing some non-State
operations (Performing REST API Call, Logging, Navigate to another Activity/Fragment for examples). These kind of operations can be thought of as a side-effect. And, to handle side-effects, we create an abstraction called MiddleWare
:
Here we encounter an unknown entity called Store
. Don't you worry, it is what orchestrates the above components involved. To orchestrate the components, Store can leverage Kotlin Coroutines, more precisely StateFlow that can help achieve collecting the state without depending on AndroidX LiveData. Below is the implementation to showcase such:
Well, dispatch()
is what triggers every component to receive some Action
and perform respective executions.
There's a reason dispatch()
chosen to be suspend
ing. You see, there can be myriad of side-effects that are blocking in nature, so it makes sense that MiddleWare
s can perform blocking operations out-of-the-box.
Lastly, since Store
's method dispatch()
is a suspend
ing method, it means this method must be executed from a CoroutineContext
. There are two options:
-
Activity
: Activity
consists of lifecycleScope
. But Activity is prone to be destroyed in the event of a configuration change or low memory.
-
ViewModel
: ViewModel
consists of viewModelScope
. Since ViewModel
at least survives the configuration change, it becomes ideal candidate to host the Store
.
Now, much of the code for ViewModel can be repetitive for every implementation with Store, so we can simplify this by creating an abstraction:
By default, dispatchActionToStore()
will always execute the blocking operations in Main (read UI) Thread. If there's a need that a blocking operation be operated under a certain thread (say IO for REST API Calls, Computation for general purpose processing and such), pass the value of dispatcher as well.
Example Implementation in Action
Since our ExampleScreen
only shows one text, so we added this attribute like this:
Our ExampleScreen
has two intent from User: Change the Input Text and Handle click of Button.
ExampleReducer
changes the ExampleState
based on the dispatched ExampleAction
from Store
.
ExampleViewModel
receives events from UI which are then transformed to ExampleAction
for dispatching this Action
to Store
.
ExampleActivity
hosts our @Composable ExampleState
.
You may ask where is ExampleMiddleWare.kt
?
Since the button is not doing anything at the moment (as evident from the ExampleReducer.kt
) and there's no UI to showcase such side-effect, it's parked for now.
Stay tuned for the next part.
Thank You!