State in an app is any value that can change over time. This is a very broad definition and encompasses everything from a Room database to a variable in a class.
All Android apps display state to the user. A few examples of state in Android apps:
- A Snackbar that shows when a network connection can't be established.
- A blog post and associated comments.
- Ripple animations on buttons that play when a user clicks them.
- Stickers that a user can draw on top of an image.
Jetpack Compose helps you be explicit about where and how you store and use state in an Android app. This guide focuses on the connection between state and composables, and on the APIs that Jetpack Compose offers to work with state more easily.
State and composition
Compose is declarative and as such the only way to update it is by calling the
same composable with new arguments. These arguments are representations of the
UI state. Any time a state is updated a recomposition takes place. As a
result, things like TextField
don’t automatically update like they do in
imperative XML based views. A composable has to explicitly be told the new state
in order for it to update accordingly.
@Composable private fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
If you run this and try to enter text, you'll see that nothing happens. That's
because the TextField
doesn't update itself—it updates when its value
parameter changes. This is due to how composition and recomposition work in
Compose.
To learn more about initial composition and recomposition, see Thinking in Compose.
State in composables
Composable functions can use the
remember
API to store an object in memory. A value computed by remember
is
stored in the Composition during
initial composition, and the stored value is returned during recomposition.
remember
can be used to store both mutable and immutable objects.
mutableStateOf
creates an observable
MutableState<T>
,
which is an observable type integrated with the compose runtime.
interface MutableState<T> : State<T> {
override var value: T
}
Any changes to value
schedules recomposition of any composable functions
that read value
.
There are three ways to declare a MutableState
object in a composable:
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
These declarations are equivalent, and are provided as syntax sugar for different uses of state. You should pick the one that produces the easiest-to-read code in the composable you're writing.
The by
delegate syntax requires the following imports:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
You can use the remembered value as a parameter for other composables or even as
logic in statements to change which composables are displayed. For example, if
you don't want to display the greeting if the name is empty, use the state in an
if
statement:
@Composable fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { var name by remember { mutableStateOf("") } if (name.isNotEmpty()) { Text( text = "Hello, $name!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) } }
While remember
helps you retain state across recompositions, the state is not
retained across configuration changes. For this, you must use
rememberSaveable
. rememberSaveable
automatically saves any value that can be
saved in a Bundle
. For other values, you can pass in a custom saver object.
Other supported types of state
Compose doesn't require that you use MutableState<T>
to hold state; it
supports other observable types. Before reading another observable type in
Compose, you must convert it to a State<T>
so that composables can
automatically recompose when the state changes.
Compose ships with functions to create State<T>
from common observable
types used in Android apps. Before using these integrations, add the
appropriate artifact(s) as outlined below:
Flow
:collectAsStateWithLifecycle()
collectAsStateWithLifecycle()
collects values from aFlow
in a lifecycle-aware manner, allowing your app to conserve app resources. It represents the latest emitted value from the ComposeState
. Use this API as the recommended way to collect flows on Android apps.The following dependency is required in the
build.gradle
file (it should be 2.6.0-beta01 or newer):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.5")
}
Groovy
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.5"
}
-
collectAsState
is similar tocollectAsStateWithLifecycle
, because it also collects values from aFlow
and transforms it into ComposeState
.Use
collectAsState
for platform-agnostic code instead ofcollectAsStateWithLifecycle
, which is Android-only.Additional dependencies are not required for
collectAsState
, because it is available incompose-runtime
. -
observeAsState()
starts observing thisLiveData
and represents its values viaState
.The following dependency is required in the
build.gradle
file:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.7.3")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.7.3"
}
-
subscribeAsState()
are extension functions that transform RxJava2’s reactive streams (e.g.Single
,Observable
,Completable
) into ComposeState
.The following dependency is required in the
build.gradle
file:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.7.3")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.7.3"
}
-
subscribeAsState()
are extension functions that transform RxJava3’s reactive streams (e.g.Single
,Observable
,Completable
) into ComposeState
.The following dependency is required in the
build.gradle
file:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.7.3")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.7.3"
}
Stateful versus stateless
A composable that uses remember
to store an object creates internal state,
making the composable stateful. HelloContent
is an example of a stateful
composable because it holds and modifies its name
state internally. This can
be useful in situations where a caller doesn't need to control the state and can
use it without having to manage the state themselves. However, composables with
internal state tend to be less reusable and harder to test.
A stateless composable is a composable that doesn't hold any state. An easy way to achieve stateless is by using state hoisting.
As you develop reusable composables, you often want to expose both a stateful and a stateless version of the same composable. The stateful version is convenient for callers that don't care about the state, and the stateless version is necessary for callers that need to control or hoist the state.
State hoisting
State hoisting in Compose is a pattern of moving state to a composable's caller to make a composable stateless. The general pattern for state hoisting in Jetpack Compose is to replace the state variable with two parameters:
value: T
: the current value to displayonValueChange: (T) -> Unit
: an event that requests the value to change, whereT
is the proposed new value
However, you are not limited to onValueChange
. If more specific events are
appropriate for the composable, you should define them using lambdas.
State that is hoisted this way has some important properties:
- Single source of truth: By moving state instead of duplicating it, we're ensuring there's only one source of truth. This helps avoid bugs.
- Encapsulated: Only stateful composables can modify their state. It's completely internal.
- Shareable: Hoisted state can be shared with multiple composables. If you
wanted to read
name
in a different composable, hoisting would allow you to do that. - Interceptable: callers to the stateless composables can decide to ignore or modify events before changing the state.
- Decoupled: the state for the stateless composables may be stored
anywhere. For example, it's now possible to move
name
into aViewModel
.
In the example case, you extract the name
and the onValueChange
out of
HelloContent
and move them up the tree to a HelloScreen
composable that
calls HelloContent
.
@Composable fun HelloScreen() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it }) } @Composable fun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello, $name", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") }) } }
By hoisting the state out of HelloContent
, it's easier to reason about the
composable, reuse it in different situations, and test. HelloContent
is
decoupled from how its state is stored. Decoupling means that if you modify or
replace HelloScreen
, you don't have to change how HelloContent
is
implemented.
The pattern where the state goes down, and events go up is called a
unidirectional data flow. In this case, the state goes down from HelloScreen
to HelloContent
and events go up from HelloContent
to HelloScreen
. By
following unidirectional data flow, you can decouple composables that display
state in the UI from the parts of your app that store and change state.
See the Where to hoist state page to learn more.
Restoring state in Compose
The rememberSaveable
API behaves similarly to remember
because it
retains state across recompositions, and also across activity or process
recreation using the saved instance state mechanism. For example, this happens,
when the screen is rotated.
Ways to store state
All data types that are added to the Bundle
are saved automatically. If you
want to save something that cannot be added to the Bundle
, there are several
options.
Parcelize
The simplest solution is to add the
@Parcelize
annotation to the object. The object becomes parcelable, and can be bundled. For
example, this code makes a parcelable City
data type and saves it to the
state.
@Parcelize data class City(val name: String, val country: String) : Parcelable @Composable fun CityScreen() { var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) } }
MapSaver
If for some reason @Parcelize
is not suitable, you can use mapSaver
to
define your own rule for converting an object into a set of values that the
system can save to the Bundle
.
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
ListSaver
To avoid needing to define the keys for the map, you can also use listSaver
and use its indices as keys:
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
State holders in Compose
Simple state hoisting can be managed in the composable functions itself. However, if the amount of state to keep track of increases, or the logic to perform in composable functions arises, it's a good practice to delegate the logic and state responsibilities to other classes: state holders.
See the state hoisting in Compose documentation or, more generally, the State holders and UI State page in the architecture guide to learn more.
Retrigger remember calculations when keys change
The remember
API is frequently used together with MutableState
:
var name by remember { mutableStateOf("") }
Here, using the remember
function makes the MutableState
value survive
recompositions.
In general, remember
takes a calculation
lambda parameter. When remember
is first run, it invokes the calculation
lambda and stores its result. During
recomposition, remember
returns the value that was last stored.
Apart from caching state, you can also use remember
to store any object or
result of an operation in the Composition that is expensive to initialize or
calculate. You might not want to repeat this calculation in every recomposition.
An example is creating this ShaderBrush
object, which is an expensive
operation:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
remember
stores the value until it leaves the Composition. However, there is a
way to invalidate the cached value. The remember
API also takes a key
or
keys
parameter. If any of these keys change, the next time the function
recomposes, remember
invalidates the cache and executes the calculation
lambda block again. This mechanism gives you control over the lifetime of an
object in the Composition. The calculation remains valid until the inputs
change, instead of until the remembered value leaves the Composition.
The following examples show how this mechanism works.
In this snippet, a ShaderBrush
is created and used as the background
paint of a Box
composable. remember
stores the ShaderBrush
instance
because it is expensive to recreate, as explained earlier. remember
takes
avatarRes
as the key1
parameter, which is the selected background image. If
avatarRes
changes, the brush recomposes with the new image, and reapplies to
the Box
. This can occur when the user selects another image to be the
background from a picker.
@Composable private fun BackgroundBanner( @DrawableRes avatarRes: Int, modifier: Modifier = Modifier, res: Resources = LocalContext.current.resources ) { val brush = remember(key1 = avatarRes) { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) } Box( modifier = modifier.background(brush) ) { /* ... */ } }
In the next snippet, state is hoisted to a plain state holder class
MyAppState
. It exposes a rememberMyAppState
function to initialize an
instance of the class using remember
. Exposing such functions to create an
instance that survives recompositions is a common pattern in Compose. The
rememberMyAppState
function receives windowSizeClass
, which serves as
the key
parameter for remember
. If this parameter changes, the app needs to
recreate the plain state holder class with the latest value. This may occur if,
for example, the user rotates the device.
@Composable private fun rememberMyAppState( windowSizeClass: WindowSizeClass ): MyAppState { return remember(windowSizeClass) { MyAppState(windowSizeClass) } } @Stable class MyAppState( private val windowSizeClass: WindowSizeClass ) { /* ... */ }
Compose uses the class's equals implementation to decide if a key has changed and invalidate the stored value.
Store state with keys beyond recomposition
The rememberSaveable
API is a wrapper around remember
that can store
data in a Bundle
. This API allows state to survive not only
recomposition, but also activity recreation and system-initiated process death.
rememberSaveable
receives input
parameters for the same purpose that
remember
receives keys
. The cache is invalidated when any of the inputs
change. The next time the function recomposes, rememberSaveable
re-executes
the calculation lambda block.
In the following example, rememberSaveable
stores userTypedQuery
until
typedQuery
changes:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
Learn more
To learn more about state and Jetpack Compose, consult the following additional resources.
Samples
Codelabs
Videos
Blogs
Recommended for you
- Note: link text is displayed when JavaScript is off
- Architecting your Compose UI
- Save UI state in Compose
- Side-effects in Compose