State and remember
A composable function can be called again and again — every time the UI needs to update, Compose re-executes the function body, a process called recomposition. Ordinary local variables declared inside a composable are re-initialized on every call, so a plain var counter = 0 would silently reset to zero on every recomposition, making it useless for tracking anything across UI updates. The remember function solves this by storing a value in the Composition itself, keyed to the call site, so the value survives recomposition as long as the composable stays in the same position in the UI tree.
Cricket analogy: A commentator who re-reads the day's opening team sheet from scratch every time they speak would forget the current score entirely; remember is like keeping a running scorebook that persists across every over instead of resetting to zero each time.
How remember Ties a Value to the Composition
Internally, remember stores its value in a slot table associated with the composable's position in the tree. On the first call, the lambda you pass to remember runs and its result is cached. On subsequent recompositions, Compose skips re-running that lambda and simply returns the cached value — unless you pass keys to remember(key1, key2) { ... }, in which case a change in any key forces the value to be recalculated. This is different from a field on a ViewModel: remembered state lives only as long as the composable remains part of the composition, so it is lost on configuration changes unless combined with rememberSaveable.
Cricket analogy: remember's slot table is like a scorer keeping the innings total pinned to a specific page in the scorebook — they only recalculate the total from scratch on the first ball, and every ball after just reads the cached figure unless the innings itself changes (a new key).
@Composable
fun ExpandableCard(title: String, body: String) {
var expanded by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { expanded = !expanded }
.padding(16.dp)
) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
if (expanded) {
Text(
text = body,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 8.dp)
)
}
}
}remember vs rememberSaveable
remember alone does not survive a configuration change such as a screen rotation, because the Activity (and with it the whole Composition) is destroyed and recreated. rememberSaveable, by contrast, writes its value into the same Bundle-based saved-instance-state mechanism used by the View system, so simple types (String, Int, Boolean, Parcelable) survive rotation automatically. For complex objects, you supply a custom Saver that knows how to serialize and restore the value.
Cricket analogy: remember alone is like a scorer's chalk marks on a portable board that get wiped clean if the board is knocked over and reset (rotation); rememberSaveable is like writing the score into the official paper scorebook that survives the board being knocked over.
@Composable
fun SurveyScreen() {
var selectedRating by rememberSaveable { mutableStateOf(0) }
Column {
Text("Rate your experience: $selectedRating")
Row {
(1..5).forEach { star ->
IconButton(onClick = { selectedRating = star }) {
Icon(
imageVector = if (star <= selectedRating) Icons.Filled.Star else Icons.Outlined.Star,
contentDescription = "Rate $star stars"
)
}
}
}
}
}A helpful mental model: remember is like a sticky note attached to a specific spot in the UI tree — it survives as long as that spot exists, but a whole new sheet of paper (a fresh Activity after rotation) means the sticky note is gone too, unless it was written with rememberSaveable's more durable ink.
Calling remember conditionally — for example inside an if block that only sometimes executes — can break Compose's slot-table position tracking and cause state to be lost or mixed up between composables. remember (like all Compose APIs) must be called unconditionally, in the same order, on every recomposition.
- Plain local variables reset on every recomposition; remember preserves a value across recompositions by storing it in the Composition's slot table.
- remember { ... } only re-evaluates its initializer once, unless you pass keys that change.
- remember does not survive configuration changes like rotation; rememberSaveable does, using the Bundle-based saved-instance-state mechanism.
- Complex types need a custom Saver to work with rememberSaveable.
- remember must be called unconditionally and in the same order every recomposition, never inside conditional branches.
- Remembered state is scoped to the composable's position, not shared globally like a ViewModel field.
Practice what you learned
1. What problem does remember solve for a composable function?
2. What is the key difference between remember and rememberSaveable?
3. Why is it problematic to call remember inside a conditional `if` block that only runs sometimes?
4. What must you supply to use rememberSaveable with a complex custom class that isn't Parcelable?
5. What controls whether remember's initializer lambda re-runs on a later recomposition?
Was this page helpful?
You May Also Like
mutableStateOf and Recomposition
Dive into how mutableStateOf creates observable state that triggers recomposition, and how Compose determines which composables need to redraw.
State Hoisting
Learn the Compose pattern of moving state up to a caller so composables become stateless, reusable, and easier to test.
derivedStateOf and Side Effects
Learn how derivedStateOf avoids unnecessary recomposition for computed values, and how effect handlers like LaunchedEffect manage side effects safely.
App Lifecycle and Process Death
How Android activities and processes move through creation, backgrounding, and destruction, and why saving state correctly is essential to survive system-initiated process death.