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 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 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 responsible rather than a central view model 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.
Again assuming independence from all other features, here are 4 main components required to make this work.
We will need:
Here is how one might implement all these components.
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.
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 it 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.
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.
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.