Stopping the Reset: Persisting Compose State with rememberSaveable
Stop losing user input and scroll positions during screen rotations. Learn how to use rememberSaveable to persist UI state across configuration changes and process death in Jetpack Compose.
18 Feb 2026, 17:49 UTC

The Rotation Reset Problem
In Jetpack Compose, using remember { mutableStateOf(...) } is the standard way to hold state during recomposition. However, this state is volatile. When a user rotates their device or the system kills the app process to reclaim memory, the Activity is destroyed and recreated. This wipes the remember cache, resetting counters, text field inputs, and scroll positions to their initial values.
The solution is rememberSaveable. While remember stores objects in the Composition, rememberSaveable stores them in a Bundle, allowing the state to survive configuration changes and system-initiated process death.
How rememberSaveable Operates
rememberSaveable behaves like remember, but it hooks into the Android onSaveInstanceState mechanism. When the Activity is paused or destroyed due to a configuration change, Compose serializes the value and saves it to the system's state registry. Upon recreation, it checks the Bundle for a saved value before falling back to the initial value provided in the lambda.
The Serialization Requirement
Because the state is stored in a Bundle, the data must be compatible with Android's serialization rules. Simple types such as Int, String, Boolean, and Float work out of the box. Attempting to save a custom data class without a way to serialize it will crash with a RuntimeException because the Bundle does not know how to handle the object.
Worked Example: A Persistent Counter
This example compares a standard remember state with a rememberSaveable state. After rotation, the first counter resets to 0, while the second maintains its value.
@Composable
fun PersistenceDemo() {
var volatileCount by remember { mutableIntStateOf(0) }
var persistentCount by rememberSaveable { mutableIntStateOf(0) }
Column(modifier = Modifier.padding(16.dp)) {
Text("Volatile: $volatileCount")
Button(onClick = { volatileCount++ }) { Text("Increment Volatile") }
Spacer(modifier = Modifier.height(20.dp))
Text("Persistent: $persistentCount")
Button(onClick = { persistentCount++ }) { Text("Increment Persistent") }
}
}
Verification Steps
- Run the app on an emulator or physical device.
- Increment both counters to a specific number, e.g., 5.
- Rotate the device 90 degrees.
- Observe that the Volatile counter returns to 0, while the Persistent counter remains at 5.
Handling Complex Objects
When state is more complex than a primitive, use Parcelable or a custom Saver.
@Parcelize
data class UserProfile(val name: String, val age: Int) : Parcelable
@Composable
fun ProfileScreen() {
var profile by rememberSaveable { mutableStateOf(UserProfile("Guest", 0)) }
}
Trade-offs and Limitations
rememberSaveable is for UI state, not a replacement for a database or ViewModel.
- Bundle size limits: The Android Bundle has a strict size limit. Saving large lists or bitmaps via
rememberSaveablecan triggerTransactionTooLargeException. - Lifecycle scope: State is tied to the Activity/Fragment lifecycle. For data that must persist across screens or app sessions, move state to a ViewModel using
SavedStateHandleor a local database. - Stale data: Because state is persisted, it may persist longer than intended if the user navigates deep and returns. Ensure state that should be fresh is not marked as saveable.
Actionable Checklist
- Use
rememberfor state that only needs to survive recomposition. - Use
rememberSaveablefor state that must survive rotation or process death. - Ensure custom objects are
Parcelableor have a definedSaver. - Keep saved data small to avoid
TransactionTooLargeException.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.