Persisting UI State in Jetpack Compose: From remember to rememberSaveable
Learn how to prevent UI state loss during Android configuration changes using remember, rememberSaveable, and derivedStateOf in Jetpack Compose.
10 Jul 2026, 07:36 UTC

The Problem: State Loss During Configuration Changes
In Jetpack Compose, a standard remember block preserves state during recomposition (when the UI refreshes because data changed). However, it fails during configuration changes, such as rotating the screen or switching to dark mode. When the Android OS destroys and recreates the Activity to apply new resources, all state held by remember is wiped, resetting the UI to its initial state.
To solve this, you must distinguish between transient state (temporary UI changes) and persistent state (data that must survive Activity recreation).
Prerequisites
- Android Studio Hedgehog or newer.
- A project targeting Compose BOM 2023.05.00 or later.
- Basic familiarity with Composable functions and the
mutableStateOfAPI.
Implementation Strategy
1. Transient State with remember
Use remember for state that only needs to exist as long as the Composable is in the Composition tree. This is ideal for animations or temporary toggle states that do not impact the user's progress if the screen rotates.
// This value resets on screen rotation
var isExpanded by remember { mutableStateOf(false) }
2. Persistent State with rememberSaveable
rememberSaveable behaves like remember but automatically saves the value into a Bundle. This ensures the state survives Activity recreation and system-initiated process death.
// This value persists after screen rotation
var textInput by rememberSaveable { mutableStateOf("") }
3. Optimizing Updates with derivedStateOf
When a UI element depends on a state that changes frequently (like a scroll position), using a standard remember block can cause excessive recompositions. derivedStateOf creates a new state object that only triggers a recomposition when the result of the calculation changes, not every time the input changes.
Example: Conditional Button Visibility
@Composable
fun ScrollToTopButton(listState: LazyListState) {
// Only triggers recomposition when the boolean result changes,
// rather than every single pixel scrolled.
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
if (showButton) {
Button(onClick = { /* Scroll to top */ }) { Text("Top") }
}
}
Comparison: State Storage Mechanisms
| Mechanism | Survives Recomposition | Survives Rotation | Survives Process Death | Storage Location |
|---|---|---|---|---|
remember |
Yes | No | No | Composition Memory |
rememberSaveable |
Yes | Yes | Yes | SavedInstanceState Bundle |
ViewModel + StateFlow |
Yes | Yes | No (unless using SavedStateHandle) | ViewModel Store |
Verification and Diagnostics
To ensure your state management is functioning correctly, perform the following checks:
- Rotation Test: Enter text into a field using
rememberSaveableand rotate the device. The text should remain. Repeat withremember; the text should disappear. - Recomposition Monitoring: Open the Layout Inspector in Android Studio. Observe the recomposition counts (the blue numbers next to Composables). If a button is flashing/recomposing on every scroll event, wrap the logic in
derivedStateOf. - Bundle Limit Check: If you are saving large lists or complex objects in
rememberSaveable, monitor the Logcat forTransactionTooLargeException. The Bundle limit is roughly 1MB for the entire app.
Rollback and State Reset
Because rememberSaveable persists state in the OS Bundle, the state will remain even if the user navigates away and returns, provided the Activity wasn't fully finished. To manually reset this state, you must explicitly update the state variable to its default value (e.g., textInput = "") via a user action or a LaunchedEffect triggered by a specific key.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.