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 on 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 in Compose
The concept of state is at the core of Compose. Consider a simple example: a screen where the user can enter their name and a greeting is displayed in response. The following code includes text for the greeting and a text field for the name input:
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = "",
onValueChange = { },
label = { Text("Name") }
)
}
}
If you run this, 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.
Composition and recomposition
A composition describes the UI and is produced by running composables. A composition is a tree-structure of the composables that describe your UI.
During initial composition, Compose will keep track of the composables that you call to describe your UI in a composition. Then, when the state of your app changes, Jetpack Compose schedules recomposition. Recomposition is running the composables that may have changed in response to state changes, and Jetpack Compose updates the composition to reflect any changes.
A composition can only be produced by an initial composition and updated by recomposition. The only way to modify a composition is through recomposition.
To learn more about initial composition and recomposition, see Thinking in Compose.
Introducing state
To update the composable, pass in a value that represents the state of the
TextField
and add code to update the state when the value of the TextField
changes.
To introduce a local state that holds the name that should be displayed, use
remember { mutableStateOf() }
, passing in the default value for the text. That
way, whenever the name
state changes, the value displayed by TextField
will
change too.
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
var name by remember { mutableStateOf("") }
Text(
text = "Hello",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
}
}
Composable functions can store a single object in memory by using the
remember
composable. A value computed by remember
is stored in the composition during
initial composition, and that stored value is returned during recomposition. You
can use remember
to store both mutable and immutable objects.
mutableStateOf
creates a MutableState
, which is an observable type in
Compose. Any changes to its value will schedule recomposition of any composable
functions that read that value.
remember
helps you preserve the state across recompositions. If you use
mutableStateOf
without also using remember
, then the state is reinitialized
to an empty string every time the HelloContent
composable is recomposed.
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.h5
)
}
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.
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
var name by rememberSaveable { mutableStateOf("") }
if (name.isNotEmpty()) {
Text(
text = "Hello, $name!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
}
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
}
}
Stateless composables
When a composable holds its own state like in the example above, it makes the composable hard to reuse and test, and it also keeps the composable tightly coupled to how its state is stored. Instead, you should make this a stateless composable—a composable that doesn't hold any state.
To do this, you can use state hoisting. State hoisting is a programming pattern where you move the state of a composable to the caller of that composable. A simple way to do this is by replacing the state with a parameter and using lambdas to represent events.
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.h5
)
OutlinedTextField(
value = name,
onValueChange = { onNameChanged(it) },
label = { Text("Name") }
)
}
}
HelloContent
has access to the state as an immutable String
parameter, as
well as a lambda onNameChange
that it can call when it wants to request the
state change.
Lambdas are the most common way to describe events on a composable. In this
example, you define an event called onNameChange
using a lambda that takes a
String
, using Kotlin's function type syntax: (String) -> Unit
. The lambda is
called onNameChange
—present tense, as the event doesn't mean the state has
already changed, but rather that the composable is requesting that the event
handler change it.
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.
ViewModel and state
In Jetpack Compose, you can use the ViewModel
to expose the state in an
observable holder (like LiveData
or Flow
) and also to handle events that
affect that state. The HelloScreen
example above would be implemented using a
ViewModel
like this:
class HelloViewModel : ViewModel() {
// LiveData holds state which is observed by the UI
// (state flows down from ViewModel)
private val _name = MutableLiveData("")
val name: LiveData<String> = _name
// onNameChanged is an event we're defining that the UI can invoke
// (events flow up from UI)
fun onNameChanged(newName: String) {
_name.value = newName
}
}
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
// by default, viewModel() follows the Lifecycle as the Activity or Fragment
// that calls HelloScreen(). This lifecycle can be modified by callers of HelloScreen.
// name is the current value of [helloViewModel.name]
// with an initial value of ""
val name: String by helloViewModel.name.observeAsState("")
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.h5
)
OutlinedTextField(
value = name,
onValueChange = { onNameChanged(it) },
label = { Text("Name") }
)
}
}
observeAsState
observes a LiveData<T>
and returns a State<T>
object that is updated
whenever the LiveData
changes. State<T>
is an observable type that Jetpack
Compose can use directly. observeAsState
will observe the LiveData only while
it is in the composition.
The line:
val name: String by helloViewModel.name.observeAsState("")
...is syntactic sugar to automatically unwrap the state object that is returned
by observeAsState
. You can also assign the state object using an assignment
operator (=
), which makes it a State<String>
instead of a String
:
val nameState: State<String> = helloViewModel.name.observeAsState("")
HelloViewModel
and HelloScreen
follow the unidirectional data flow pattern
where state flows down from HelloViewModel
, and events flow up from
HelloScreen
.
Consider the UI event loop for this screen:
- Event:
onValueChange
is called in response to the user typing a character. - Update state:
HelloViewModel.onNameChanged
handles processing, then sets the state of the mutableLiveData
,_name
. - Display state: The value of
HelloViewmodel.name
changes, which is observed by Compose inobserveAsState
. Then,HelloScreen
runs again (or recomposes) to describe the UI based on the new value ofname
.
See Architecting your Compose UI to learn more about how the unidirectional data flow can be implemented using ViewModel and Jetpack Compose.
Using remember
Composable functions can store a single object in memory by using the
remember
composable. 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.
Use remember to store immutable values
You can store immutable values when caching expensive UI operations, such as
computing text formatting. The remembered value is stored in the Composition
with the composable that called
remember
.
@Composable
fun FancyText(text: String) {
// by passing text as a parameter to remember, it will re-run the calculation on
// recomposition if text has changed since the last recomposition
val formattedText = remember(text) { computeTextFormatting(text) }
/*...*/
}

FancyText
with
formattedText
as childUse remember to create internal state in a composable
When you store a mutable object using remember
, you add state to a composable.
You can use this approach to create internal state for a single stateful
composable.
We strongly recommend that all mutable state used by composables be observable.
This allows compose to automatically recompose whenever the state changes.
Compose comes with a built-in observable type
State<T>
which is directly
integrated into the Compose runtime.
A good example of internal state in a composable is an ExpandingCard
that
animates between collapsed and expanded when the user clicks a button.

ExpandedCard
composable animates
between collapsed and expandedThis composable has one important state: expanded
. When expanded
the
composable should show the body, and when collapsed it should hide the body.

ExpandingCard
with
expanded
state as childYou can add a state expanded
to a composable by remembering
mutableStateOf(initialValue)
.
@Composable
fun ExpandingCard(title: String, body: String) {
// expanded is "internal state" for ExpandingCard
var expanded by remember { mutableStateOf(false) }
// describe the card for the current state of expanded
Card {
Column(
Modifier
.width(280.dp)
.animateContentSize() // automatically animate size when it changes
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
) {
Text(text = title)
// content of the card depends on the current value of expanded
if (expanded) {
// TODO: show body & collapse icon
} else {
// TODO: show expand icon
}
}
}
}
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
will schedule recomposition of any composable functions
that read value
. In the case of ExpandingCard
, whenever expanded
changes,
it causes ExpandingCard
to be recomposed.
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.
You can use the value of internal state in a composable as a parameter to
another composable, or even to change what composables are called. In
ExpandingCard
an if-statement will change the content of the card based on the
current value of expanded
.
if (expanded) {
// TODO: show body & collapse icon
} else {
// TODO: show expand icon
}
Modify internal state in a composable
State should be modified by events in a composable. If you modify state when running a composable instead of in an event, this is a side-effect of the composable, which should be avoided. For more information about side-effects in Jetpack Compose, see Thinking in Compose.
To complete the ExpandingCard
composable, let’s display the body
and a
collapse button when expanded
is true
and an expand button when expanded
is false
.
@Composable
fun ExpandingCard(title: String, body: String) {
var expanded by remember { mutableStateOf(false) }
// describe the card for the current state of expanded
Card {
Column(
Modifier
.width(280.dp)
.animateContentSize() // automatically animate size when it changes
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
) {
Text(text = title)
// content of the card depends on the current value of expanded
if (expanded) {
Text(text = body, Modifier.padding(top = 8.dp))
// change expanded in response to click events
IconButton(onClick = { expanded = false }, modifier = Modifier.fillMaxWidth()) {
Icon(Icons.Default.ExpandLess)
}
} else {
// change expanded in response to click events
IconButton(onClick = { expanded = true }, modifier = Modifier.fillMaxWidth()) {
Icon(Icons.Default.ExpandMore)
}
}
}
}
}
In this composable, the state is modified in response to onClick
events. Since
expanded
is using var
with the
property delegate
syntax, the onClick
callbacks can assign expanded
directly.
IconButton(onClick = { expanded = true }, /* … */) {
// ...
}
We can now describe the UI Update Loop for ExpandingCard
to see how internal
state is modified and used by Compose.
- Event:
onClick
is called in response to the user tapping on one of the buttons. - Update state:
expanded
is changed in theonClick
listener using assignment. - Display state:
ExpandingCard
recomposes becauseexpanded
isState<Boolean>
that changed, andExpandingCard
reads it in the lineif(expanded)
.ExpandingCard
then describes the screen for the new value ofexpanded
.
Use other types of state in Jetpack Compose
Jetpack Compose doesn't require that you use MutableState<T>
to hold state.
Jetpack Compose supports other observable types. Before reading another
observable type in Jetpack Compose, you must convert it to a State<T>
so that
Jetpack Compose can automatically recompose when the state changes.
Compose ships with functions to create State<T>
from common observable types
used in Android apps:
You can build an extension function for Jetpack Compose to read other observable
types if your app uses a custom observable class. See the implementation of the
builtins for examples of how to do this. Any object that allows Jetpack Compose
to subscribe to every change can be converted to State<T>
and read by a
composable.
You can also build integration layers for non-observable state objects by using
invalidate
to manually trigger recomposition. This should be reserved for situations where
you must interoperate with a non-observable type. Using invalidate
is easy
to get wrong and tends to lead to complex code that's harder to read than the
same code using observable state objects.
Separate internal state from UI composables
The ExpandingCard
in the last section has internal state. As a result, the
caller cannot control the state. This means, for example, that if you wanted to
start an ExpandingCard
in the expanded state, there is no way to do so. You
also can't make the card expand in response to another event, such as the user
clicking on a Fab
. It also means that if you wanted to move the expanded
state into a ViewModel
, you couldn't do it.
On the other hand, by using internal state in ExpandingCard
, a caller that
doesn't need to control or hoist the state can use it without having to manage
the state themselves.
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.
To provide both as a stateful and stateless interfaces, extract a stateless composable that displays the UI using state hoisting.
Notice that both composables are both named ExpandingCard
even though they
take different parameters. The naming convention for composables that emit UI
is a capital-case noun that describes what the composable represents on the
screen. In this case, they both represent an ExpandingCard
. This naming
convention applied throughout the Compose libraries, for example in
TextField
and TextField
.
Here's ExpandingCard
split into stateful and stateless composables:
// this stateful composable is only responsible for holding internal state
// and defers the UI to the stateless composable
@Composable
fun ExpandingCard(title: String, body: String) {
var expanded by remember { mutableStateOf(false) }
ExpandingCard(
title = title,
body = body,
expanded = expanded,
onExpand = { expanded = true },
onCollapse = { expanded = false }
)
}
// this stateless composable is responsible for describing the UI based on the state
// passed to it and firing events in response to the buttons being pressed
@Composable
fun ExpandingCard(
title: String,
body: String,
expanded: Boolean,
onExpand: () -> Unit,
onCollapse: () -> Unit
) {
Card {
Column(
Modifier
.width(280.dp)
.animateContentSize() // automatically animate size when it changes
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
) {
Text(title)
if (expanded) {
Spacer(Modifier.height(8.dp))
Text(body)
IconButton(onClick = onCollapse, Modifier.fillMaxWidth()) {
Icon(Icons.Default.ExpandLess)
}
} else {
IconButton(onClick = onExpand, Modifier.fillMaxWidth()) {
Icon(Icons.Default.ExpandMore)
}
}
}
}
}
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 like
ExpandingCard
does with onExpand
and onCollapse
.
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 for
expanded
. This helps avoid bugs. - Encapsulated: only stateful
ExpandingCard
will be able to modify its state. It's completely internal. - Shareable: hoisted state can be shared with multiple composables. Say we
wanted to hide a
Fab
button when theCard
is expanded, hoisting would allow us to do that. - Interceptable: callers to the stateless
ExpandingCard
can decide to ignore or modify events before changing the state. - Decoupled: the state for the stateless
ExpandingCard
may be stored anywhere. For example, it's now possible to movetitle
,body
, andexpanded
into aViewModel
.
Hosting this way also follows unidirectional data flow. The state is passed down from the stateful composable, and events flow up from the stateless composable.

ExpandingCard
Restore UI state after activity or process recreation
Use rememberSaveable
to restore your UI state after an activity or process
is recreated. rememberSaveable
retains state across recompositions.
In addition, rememberSaveable
also retains state
across activity and process recreation.
@Composable
fun MyExample() {
var selectedId by rememberSaveable<String?> { mutableStateOf(null) }
/*...*/
}
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.
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)
@Composable
fun MyExample() {
var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) }
}
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, nameKey to it.country) },
restore = { City(it[nameKey] as String, it[countryKey] as String) }
)
}
@Composable
fun MyExample() {
var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) }
}
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 MyExample() {
var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) }
/*...*/
}
Learn more
To learn more about state and Jetpack Compose, take the Using State in Jetpack Compose codelab.