Managing State in Jetpack Compose

Learn the differences between stateless and stateful composables and how state hoisting can help make your composables more reusable. By Rodrigo Guerrero.

5 (2) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Creating Stateful Composables

State is any value that can change during the execution of your app. It can include a value your user enters, data fetched from a database or a selection of options in a form.

Compose provides remember(), a function you can use to store a single object in memory. During the first composition run, remember() stores the initial value.

In each recomposition, remember() returns the stored value so the composable can use it. Whenever the stored value has to change, you can update it and remember() will store it. The next time a recomposition occurs, remember() will provide the latest value.

Creating Composable for Text Fields

It’s time to start using remember(). Open RegisterUserComposables.kt. In EditTextField(), add the following code at the beginning of the function:

// 1.
val text = remember {
  // 2.
  mutableStateOf("")
}

In this code, you:

  1. Create the variable using remember(). text will keep a String value through recompositions.
  2. Use a mutableStateOf() with an empty text as initial value.

Now, use the variable you just created. Replace the value and onValueChange() within the OutlinedTextField() with the following:

// 1.
value = text.value,
// 2.
onValueChange = { text.value = it },

In this code, you:

  1. Set the value of remember() to the OutlinedTextField. Because it’s a mutableState, call its value property.
  2. Update the value stored in the mutable state of remember() whenever the value in the OutlinedTextField changes.

Build and run. Open the registration form and add an email and username. You can see the text fields show the values that you enter, like in the following image:

Stateful EditTexts

Creating Radio Button Composable

remember() can hold state with any value type. Go to RadioButtonWithText() and add the following code at the beginning of the function:

val isSelected = remember {
  mutableStateOf(false)
}

In this case, remember() will hold a Boolean mutable state that indicates whether the user has selected the radio button. Now, update the RadioButton composable with isSelected:

RadioButton(
  // 1.
  selected = isSelected.value,
  // 2.
  onClick = { isSelected.value = !isSelected.value }
)

Similar to the previous code, here you:

  1. Set the radio button selected property with the value from remember().
  2. Change the value of isSelected whenever the user clicks the radio button.

Build and run again. Open the registration form. Now you can select or unselect the radio buttons too. However, you can have both radio buttons selected at the same time. You’ll fix this later in the tutorial.

Stateful Radio Buttons

Creating Composable for DropDown Menu

Finally, you’ll make DropDown a stateful composable. Add the following code at the top of the DropDown composable:

// 1.
val selectedItem = remember {
  mutableStateOf("Select your favorite Avenger:")
}
// 2.
val isExpanded = remember {
  mutableStateOf(false)
}

In this case, you need to use remember() twice:

  1. selectedItem will hold the value of the item the user selects from the drop-down menu. You also provide a default value.
  2. isExpanded will have the expanded state of the drop-down.

Add the following line of code to the row modifier, below .padding(vertical = 16.dp):

.clickable { isExpanded.value = true }

With this line, you set the value of isExpanded to true whenever the user clicks the drop-down menu, making it expand and show its contents. Update the line with Text("") like this:

Text(selectedItem.value)

This way, you set the selectedItem value to the Text() composable so users can see the value they selected once they dismiss either the drop-down menu or the default value if they haven’t selected anything yet.

Within DropdownMenu, modify the line expanded = false as shown below:

expanded = isExpanded.value,

This makes DropdownMenu know whether it needs to expand. Now, update the line onDismissRequest = { }, as follows:

onDismissRequest = { isExpanded.value = false },

With this line, you collapse the drop-down menu whenever it receives a dismiss request.

Finally, you have to implement the code when users select their favorite Avenger. Update the onClick content within the DropdownMenuItem() as follows:

onClick = {
  // 1.
  selectedItem.value = menuItems[index]
  // 2.
  isExpanded.value = false
}

In this code, you:

  1. Set the selected Avenger name to selectedItem.
  2. Collapse the drop-down menu after the user selects an item.

Build and run. Tap on the drop-down menu and select your favorite Avenger. Once you select it, the drop-down collapses, and you can see the name of the Avenger you selected. Great work!

Stateful Drop-down

Composables that use remember() to create and store state are stateful components. Each component stores and modifies its state.

Having stateful components is useful when a caller doesn’t need to know or modify the composable’s state. However, these components are difficult to reuse. And, as you saw with the radio buttons, it’s not possible to share state between composables.

When you need a component whose caller needs to control and modify its state, you need to create stateless composables.

Creating Stateless Composables

Compose uses the state hoisting pattern to make composables stateless. State hoisting moves the composable’s state to its caller.

However, the composable still needs to have values that can change and emit events whenever an action takes place. You can replace the state with two types of parameters:

  • value: In this variable, you receive the value to display in your composable.
  • onEventCallback: Your composable will call each onEventCallback() for each event it needs to trigger. This way, the component lets its caller know that an action occurred.

Each composable can have many value parameters and many event callbacks. Once the composable is stateless, someone needs to manage the state.

State Holders

A ViewModel can hold the state of the composables in a view. The ViewModel provides the UI with access to the other layers, like the business and data layers. Another advantage is that ViewModels have longer lifetimes than the composables, so it makes them a good place to hold the UI state.

You can then define the state variables using LiveData, Flow or RxJava and define the methods that will change the state of these variables. You can take a look at FormViewModel.kt and MainViewModel.kt to see iState‘s implementation of state holders.

Next, you’ll start implementing state hoisting.