On this page
A user types a long review, rotates the phone to see it better, and the text field is empty. Or they switch to their banking app to copy an OTP, come back, and the screen has reset. Both bugs come from choosing remember where rememberSaveable (or a ViewModel) was needed.
What each one survives
| Event | remember | rememberSaveable | ViewModel | ViewModel + SavedStateHandle |
|---|---|---|---|---|
| Recomposition | โ | โ | โ | โ |
Leaving composition (e.g. if hides it) | โ | โ (unless in a saveable state holder, as in navigation) | โ | โ |
| Configuration change (rotation, dark mode, locale) | โ | โ | โ | โ |
| Process death (system kills app in background) | โ | โ | โ | โ |
| User closes app from recents / back from root | โ | โ | โ | โ |
For data that must survive even the user closing the app, such as drafts or settings, persist it with Room or DataStore.
How rememberSaveable works
rememberSaveable stores its value in the saved instance state Bundle, the same mechanism Activities use. The value must therefore be saveable: primitives, Strings, and Parcelable or Serializable types, or you provide a Saver.
@Composable
fun ReviewForm() {
var text by rememberSaveable { mutableStateOf("") } // String: saveable
var rating by rememberSaveable { mutableIntStateOf(0) } // Int: saveable
var showTips by remember { mutableStateOf(true) } // transient UI hint: remember is fine
// ...
}Custom types with Parcelize
@Parcelize
data class DateRange(val startEpochDay: Long, val endEpochDay: Long) : Parcelable
var range by rememberSaveable { mutableStateOf(DateRange(0, 0)) }This requires the kotlin-parcelize Gradle plugin.
Custom Savers
data class Filters(val query: String, val onlyUnread: Boolean)
val FiltersSaver = listSaver<Filters, Any>(
save = { listOf(it.query, it.onlyUnread) },
restore = { Filters(it[0] as String, it[1] as Boolean) },
)
var filters by rememberSaveable(stateSaver = FiltersSaver) { mutableStateOf(Filters("", false)) }Keys: resetting remembered values
@Composable
fun ProductScreen(productId: String) {
// Recreated when productId changes, otherwise reused
val formatter = remember(productId) { PriceFormatter(productId) }
// Saved selection resets when navigating to a different product
var selectedSize by rememberSaveable(productId) { mutableStateOf<String?>(null) }
}Limits and when to use a ViewModel instead
- Bundle size is limited. Saving large lists or bitmaps can cause
TransactionTooLargeException. Save IDs or small input, and reload data from the repository. - Business logic doesn't belong in composables. If state needs loading, validation or saving, put it in a ViewModel, using
SavedStateHandlefor the parts that must survive process death. - Don't save derived values. Save the inputs and recompute the rest.
How to test process death
- Run the app, type into the form, then press Home.
- Run
adb shell am kill com.your.package(it only kills background apps), or enable Don't keep activities. - Reopen the app from recents. Everything in
rememberSaveableandSavedStateHandleshould be restored.
Choosing in practice
Text the user typed
rememberSaveable, or SavedStateHandle in the ViewModel if the screen logic uses it.
Animation or tooltip flags
remember. Losing it on rotation is harmless.
Scroll position
rememberLazyListState() already uses a saver internally.
Frequently asked questions
Does rememberSaveable survive navigating away and back?
Inside Navigation Compose or Navigation 3 destinations, saveable state is kept for back stack entries, so returning to a screen restores it. Outside such holders, leaving composition discards it.
Is rememberSaveable slower?
Only slightly: it registers with the saved-state registry. Use it where survival matters, not for every value.
Can I rememberSaveable a ViewModel?
No. ViewModels have their own lifecycle. Use viewModel() or hiltViewModel() instead.
Conclusion
remember survives recomposition. rememberSaveable also survives configuration changes and process death, via the Bundle. Use saveable state for user input, ViewModels with SavedStateHandle for screen logic, and Room or DataStore for anything that must outlive the session.
Next step: run work at the right time with Side Effects in Compose.
Official references
๐ฑ Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
Walzi4K wallpapers for lock and home screenGet it โธ
NotiVaultNotification history: recover dismissed notificationsGet it โธ
Ready to build real Android applications?
Jetpack Compose clicks once you build real screens with real state. Practise modern Android UI development on projects that go beyond toy examples.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.