Do you know the ways to represent states in Jetpack Compose?

Yair Carreno
5 min readJul 5, 2022

--

In Compose and declarative views in general, states could represent different types of UI structures, as shown in figure 1 below.

Figure 1 Structures represented by states
  • Property UI’s state: They are primitive variables represented as states. In figure 1, text input fields such as name, phone, or address 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, and SuccessValidated.
  • 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, or Load 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 set
var phone by mutableStateOf("")
private set
var address by mutableStateOf("")
private set
var 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:

Figure 2: Multiple events

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:

Figure 3: Grouping events

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.

Figure 4: Building e-commerce with Compose for Android

--

--

Yair Carreno

Software engineer with a technical blog about #iOS, #Android, #Angular. Author of “The Clean Way to Use Rx”: https://leanpub.com/the-clean-way-to-use-rx