Kotlin Flow with Clean Architecture and MVVM Pattern in Android
The main goal when developing an Android application is to write a code easy to test and painless to maintain. We must use an architecture which makes it effortless. To do so, we should follow the Separation of Concerns. It helps us to have an efficient application design by separating logic, data and UI.
Kotlin Flow is used to stream data from API-services to UI-based classes. It can apply any logic before deserving data to the UI components. This library suits to the Clean Architecture’s design.
Clean Architecture 🧞
This architecture comes from the well-known Robert C. Martin, called “Uncle Bob”. In his book, he provided the guidance to write successful architecture by separating our application code in layers.
The objective is to separate the responsibility, making it testable and avoid any strong dependencies to UI, frameworks, databases. We could change a dependency smoothly without affecting the whole structure.
Each circle (or layer) should not be aware of the upper one. The most inner circle takes care of the business logic and API connections. The next one named usecases contains the application logic. Followed by the upper circle which controls the data to pass to the views. And finally, the outer circle holds the UI components.
To learn more, Android developers team released an excellent guide for their eco-system.
MVVM pattern 🧞♀️
This architecture’s design can be combined with any pattern out there: MVC, MVP, MVI, MVVM, etc. Each of these MV* patterns results of the wish to separate the responsibilities by specific areas in our applications and to become independent.
In Android, we can implement the MVVM (Model-View-ViewModel) pattern and use the ViewModel class to manage the UI data with lifecycle awareness.
- The Model is any classes relating to data access layer, either domain data models or layer services.
- The ViewModel is the entry point of the data access layer. It is used to interact with the Model and bound to the View.
- The View managers (Activities, Fragments) should not contain any other logic than view logic (showing/hiding elements, component click listeners, etc). They observe the data from the ViewModel and change the components accordingly.
Kotlin Flow integration 🧞♂️
A flow should be created in the inner layer, near to the API services, passed into each circle to transform its data and it could be collected (started) from the controllers.
EServices: This is where the flows are created. These classes send and receive data. They can map the response into a DTO object.
Repositories: They apply the business logic to the Services flows.
UUseCases: They are the reflection of the user interactions. Each usecase is an action. They use the Repositories flows and defined the application logic.
VViewModels: These controllers are the entry point of the Android Framework. They collect the data by launching and starting the flows. The VMs are responsible of the flows lifecycle.
AActivities/Fragments: Managers of the view components, they observe the changes from the ViewModels and update the UI components. They don’t use the flows directly.
A case study 👩🔬
Assuming our application should get user data from an id
, an external API could return a JSON object containing a name
, a date in milliseconds named birth
and a city
:
{ "name":"John", "birth":1023073980000, "city":"London" }
In this app, we want to display the user’s name, its current city, its birthday and its age. However, we need to calculate its age based on milliseconds and we would like to render its birthday in a human readable way.
Of course, we should simply doing it with suspended functions, but let see how to realize this case study using Kotlin Flow with Clean Architecture and MVVM Pattern in Android.
Service 🤾♀️
The Service is responsible of the flow’s creation.
We create a Flow using the flow
builder which allows us to call a suspended function getUserData
of our API service to get the data in result
variable.
With the Gson library, we parse the data to a DTO object named UserDto
where all its fields are optional.
Then, we send this DTO object thanks to emit
inside our flow.
Repository 🤼♀️
The Repository handles the business logic.
It calls the fetchUserData
function from our UserService
and converts the received flow of DTO object to a flow of domain object.
In the map
operator, we calculate the user’s age from the birth
date and we create the domain class User
. Because its fields are non-nullable, we check if the ones from UserDto
are null or empty and apply a default value if they are.
In the domain class, we also have a default birthday
value which will be established in the upper layer when dealing with app logic.
Use Case ⛹️♀️
In our usecase, we define the application logic.
For contract-less usecases, we can use invoke
operator to directly perform the action when instantiate. Here, we determine the birthday
value and add it to the domain object received from our Repository. It will be displayed like: 3 Jun 2002
.
We also change the context of the flow by using flowOn
. Note that we inject a default CoroutineDispatcher used by this operator. This allows us to control the context of this flow when writing unit tests.
ViewModel 🚴♂️
Then, the ViewModel launches the flow’s coroutine and starts the collecting.
When calling the UseCase, it updates a LiveData
to propagate the new collected value to the observers in onEach
. Then, for this example, we add catch
operator to print the error if anything happened in the upstream flows. After that, we use launchIn
(contraction of launch
and collect
) to start the flow’s collection in the ViewModel’s scope.
Activity 🤸♀️
Finally, our Activity, as UI managers, observes the LiveData
and updates the view components.
We observe the VM's LiveData
by getting the userData
property. Then, we update each dedicated TextViews when receiving the new values. Lastly, we launch the flow when the Activity is creating, after the observer set, with the ViewModel fetchUserData
function.
Kotlin Flow - Clean Architecture - MVVM Pattern 🪂
With the separation of responsibilities, it is easy to implement a specific layer’s logic to a flow. Kotlin Flow can be included in this architecture and be modified, transformed and manipulated by each layers. It makes the maintaining and the tests effortless.
If you found this post helpful, feel free to clap! 👏 Thanks for reading.