Redux Style State Management for Android Apps
Use the best architectural practices in your Android apps
This article says Redux style state management but to be honest, the following ideas are not necessarily Redux style, they are rather inspired by it so it’s okay if you are unfamiliar with Redux. Also, “feature” in this context is used to describe a screen or UI that the user interacts with.
State and State Management
State Management is a way to facilitate communication and data transfer between components by centralizing all the state of various UI controls to handle data flow across the application. It is one possible manifestation of the Single Source of Truth paradigm, that is, the state is stored in a single object and that single object serves as the only true source of state for our application.
State then in the simplest terms is whatever data triggers a UI change. For a social media application, one example of state would be a post, it forces the UI to update.
Within that Post, we might have properties like image, caption, time, and likes. If we assume that image and caption are immutable then at the ‘Post level’ those two wouldn’t be considered as a state because once set they never really change. Time and Likes would be our ‘Post level’ state because they are mutable and changing and they force the UI to refresh or update. So in this sensed state is not limited to Objects or user-defined data structures like a social media Post but it extends to primitive types like the number of likes a post has (Integer).
State Management and Architecture
State Management is not independent of app architecture. As a matter of fact, State Management is because of app architecture. According to the SOLID principles an exceptional architecture is one within which modules are closed for modification and open for extension. That means when a new feature is to be added to an app, modules should not be modified to accommodate the new feature, but rather code should be added to the modules that implement the new feature, and the added code should preserve the invariant that modules are closed for modification and open for extension.
If users can interact with the new feature then it is STATEFUL. Having centralized state management means no other parts of the app will be modified to accommodate the new feature’s state but that state will be added as part of the central state from which our new feature will access its state.
Visualizing the above scenario is not easy but the point it makes is that the need for state management becomes obvious as applications grow in complexity (as new features are added). This is why it is a necessary first step to think about State Management and how to do it in a way that is scalable. Before diving into any more detail let’s first think about the properties surrounding the state and how to abstract them in Android.
If each screen in your Android app represents a feature then it is an architectural best practice to avoid the sharing of data or communication between different features. This can be further enforced by separating features into independent modules. Unlike State Management in a web app where we have a central state repository that communicates with every component in the app, here we have multiple state repos for every feature.
Take view models for example. Ut usually makes sense to have separate view models for all features in your app rather than a central view model responsible for all features in your app. This way, if the state is defined as a property of a certain feature then no two features depend on one another since each feature has its own separate state and state management.
The following treatment assumes the case described above that each feature is independent and has its own state management.
Assume we are building a social media app like Facebook and our task is to build the user profile feature where a user can see details about their profile such as their profile pic, username, bio, friends, and list of previous posts.
At this point, we are not concerned with the backend and information retrieval from an API but are more interested in how to handle this specific profile feature, how it’s rendered, and how it responds to user actions.
Key components
Again assuming independence from all other features, here are 4 main components required to make this work.
We will need:
- The Main Profile View — This is the main screen or feature of the profile which renders all profile-related data. Our main frontend component.
- Profile View Model — This gets all the data from our repositories and data sources as well as data stores [shared preferences] and provides a state object that will be used by our Profile View.
- Profile View State — An immutable data class representing the state which maps all data received by the view model and transforms it into a single state object where the properties of this object are the data. Since it is immutable, when one piece of data changes a new state object with the relevant updates is created and returned rather than modifying existing state, a feature borrowed from REDUX.
- Profile Actions — This includes abstract signatures of all possible actions that can be performed by a user while they are within the scope of the Profile View. These actions could be, Edit Profile Photo, Update username, delete a post, unfriend a friend, and navigate to settings, etc. For a login screen, some actions might be Login and Forgot Password.
Here is how one might implement all these components.
Actions
First, define all actions that can be made from within the Profile feature. The actions will depend on the type of feature you are developing. For this example we might have something like this:
To edit a profile the EditProfile
action requires a user to edit in the first place and likewise to Unfollow, the action requires an id of the user to unfollow.
We use data classes for this and for actions that require no special data we use objects. All actions within the ProfileAction
have to explicitly extend it, the reason why will become obvious soon.
Profile View State
This is the object holding all states used by the user profile feature. It is immutable thus changes to any of the data it holds will force the creation of a new instance of the view state containing the modified data. Here’s one way to implement it.
We initialize all properties of the ProfileViewState
to some default values that essentially represent an empty state.
The Profile ViewModel
The ViewModel
exposes only state to the view (the Profile screen). The implementation of the ProfileViewModel
is fairly complex so let’s start with the implementation followed by a brief explanation.
A curious reader will do further reading on some of the concepts I’m about to introduce because I simply cannot go into any detail without confusing you — assuming you are unfamiliar with Kotlin coroutines and flow.
If you are really curious then you should definitely check out my app here, GitHub. Play around with it then check out the code, It will be really helpful.
Woah! that’s a lot. The key thing to understand here is that the ProfileViewModel
is responsible for creating the state and executing any actions that are dispatched from the profile view.
It does this by using interactors and observers already implemented in the data layer of your application. Going further than this would be inconvenient.
The Profile View
This is the main UI component of the profile feature implemented using Jetpack compose.
The profile view makes use of one state object that contains all the data it requires to describe it. Actions are sent back to the ProfileViewModel
using a dispatcher where they are handled. This way the Profile view is only responsible for describing how data is laid out, actions, and state are handled by the ViewModel
. That’s pretty cool if you ask me.
State management comes in handy as applications grow in complexity, so it is a necessary step to think about how you are going to handle state at the very start of your project. This was an example of one way to do it, you don’t have to understand everything here, just the core idea is enough.
This was a really long article and if you got this far well done. Thanks for hanging out. Stay sharp!