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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Managing State in Jetpack Compose
30 mins
- Getting Started
- Introducing Jetpack Compose
- Understanding Unidirectional Data Flow
- Learning About Recomposition
- Creating Stateful Composables
- Creating Composable for Text Fields
- Creating Radio Button Composable
- Creating Composable for DropDown Menu
- Creating Stateless Composables
- State Holders
- Implementing State Hoisting
- Implementing State Hoisting in Other Composables
- Implementing the Register and Clear Buttons
- Fixing the Users List
- Where to Go From Here?
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:
- Create the variable using
remember()
.text
will keep aString
value through recompositions. - 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:
- Set the value of
remember()
to theOutlinedTextField
. Because it’s amutableState
, call itsvalue
property. - Update the value stored in the mutable state of
remember()
whenever the value in theOutlinedTextField
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:
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:
- Set the radio button
selected
property with the value fromremember()
. - 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.
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:
-
selectedItem
will hold the value of the item the user selects from the drop-down menu. You also provide a default value. -
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:
- Set the selected Avenger name to
selectedItem
. - 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!
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 ViewModel
s 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.