Do you know the ways to represent states in Jetpack Compose?
In Compose and declarative views in general, states could represent different types of UI structures, as shown in figure 1 below.
- Property UI’s state: They are primitive variables represented as states. In figure 1, text input fields such as
name
,phone
, oraddress
are of this type. - Component UI’s state: Represents the states associated with a component that groups related UI elements. For example, on the “Order Screen”, a component called ContactInformationForm could group the required data, such as contact information. This component could have states of
NameValueChanged
,PhoneValueChanged
, andSuccessValidated
. - Screen UI’s state: It represents the states associated with a screen that can be treated as absolute and independent states; for instance, a screen called OrderScreen could have the following states:
Loading
,Loaded successfully
, orLoad failed
.
Now let’s see what implementation options exist in Android and Kotlin to define these states.
Property UI’s state
They are states declared from a primitive type variable, such as String, Boolean, List, or Int, among others.
If it is declared in ViewModel (ViewModel as a Source of truth), its definition could be like this:
var name by mutableStateOf("")
private setvar phone by mutableStateOf("")
private setvar address by mutableStateOf("")
private setvar payEnable by mutableStateOf(false)
private set
If it is declared in View (View as a Source of truth), its definition in Composable could be like this:
var name by remember { mutableStateOf("") }
var phone by remember { mutableStateOf("") }
var address by remember { mutableStateOf("") }
var payEnable by remember { mutableStateOf(false) }
“remember” is a Composable that temporarily allows you to hold the state of the variable during recomposition.
As it is a Composable, this property can only be defined in declarative views, that is, in Composable functions.
Always remember that to use delegation through the “by” keyboard, you need to import:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
We have only talked about representing properties or variables through states using mutableStateOf component. However, it is also possible that data streams can be represented as states and observed by Composables. These additional options are related to Flow, LiveData o RxJava. In my book Building Modern Apps for Android, I talk a little more about that topic.
Component UI’s state
When you have a set of interrelated UI elements, their states could be grouped into a single structure or UI component with a single state.
In figure 1, for instance, the elements User name, Phone number, Address, and even Pay Order button could be grouped into a single UI component and its states represented in a single state called, for example, FormUiState.
data class FormUiState(
val nameValueChanged: String = "",
val phoneValueChanged: String = ""
val addressValueChanged: String = ""
)val FormUiState.successValidated: Boolean get() = nameValueChanged.length > 1 && phoneValueChanged.length > 3
In this case, modeling multiple states in a consolidated class of states works very well since the variables are related and even define the value of other variables. For example, this happens with the successValidated
variable, which depends on the nameValueChanged
and phoneValueChanged
variables.
Consolidating states adds benefit to implementation, centralizes control, and organizes code. It will be the technique that will be used most frequently in our implementation.
Screen UI’s state
If what is required is to model states that can be independent and to be part of the same family, you could use the following definition:
sealed class OrderScreenUiState {
data class Success(val order: Order): OrderScreenUiState()
data class Failed(val message: String): OrderScreenUiState()
object Loading: OrderScreenUiState()
}
That type of implementation is proper when working with absolute and exclusive states; you have one state or another, but not both at the same time.
Generally, simple screens of this type, such as the OnboardignScreen or ResultScreen, can be modeled with these states.
When the screen is more complex and contains many UI elements that operate independently and have multiple relationships, I recommend that the reader prefer the definition of states with the Property UI’ state and Component UI’ state techniques.
Modeling and grouping events
Returning to the OrderScreen example, we will now look at modeling Events and how to group them similarly to States.
Consider a screen like the one shown in the following figure 2:
ViewModel exposes four operations (events) to the view, each used by a View UI element.
Analyzing the four events is related to a form for entering the user’s contact information, so it makes sense to think of grouping them into a single type of event, as shown in the following figure 3:
The implementation to represent the different types of events could be like this:
sealed class ContactFormEvent {
data class OnNameChange(val name: String): FormUiEvent()
data class OnPhoneChange(val phone: String): FormUiEvent()
data class OnAddressChange(val address: String): FormUiEvent()
object PayOrder: FormUiEvent()
}
Finally, you don’t have to be so strict when simplifying states or events. It is necessary to analyze the advantages and disadvantages of each use and make the corresponding decisions.
For those related UI components, having them grouped makes a lot of sense; some other cross-cutting elements will be healthier to leave them independent.
And that’s it. In my book “Building Modern Apps for Android,” I talk a little more about that topic and other techniques.