Choosing Between remember, rememberSaveable, and ViewModel for UI State in Jetpack Compose
Learn when to use remember, rememberSaveable, or ViewModel for Jetpack Compose UI state based on configuration‑change and process‑death survival needs.
09 Oct 2026, 11:19 UTC

Decision and constraints
When building a Compose UI you often need to keep a value across recompositions, screen rotations, or even when the system kills your app’s process. The three main mechanisms are:
remember– holds a value only for the lifetime of the composition.rememberSaveable– stores the value in a Bundle so it survives configuration changes and, if the type is parcelable, process death.ViewModel(withsavedStateHandle) – scoped to a Navigation graph or Activity, survives configuration changes and process death without requiring the UI type to be parcelable.
The decision hinges on two constraints: how long the state must survive and whether the state can be represented as a Bundle‑compatible type.
Comparison table
| Mechanism | Survives recomposition | Survives configuration change | Survives process death | Boilerplate |
|---|---|---|---|---|
remember | Yes | No | No | None |
rememberSaveable | ||||
ViewModel + savedStateHandle | Yes | Yes | Yes | ViewModel dependency, small overhead |
Trade‑offs
- remember – fastest, zero allocation, but UI state is lost on rotation or multi‑window resize.
- rememberSaveable – adds serialization cost; only works with primitives,
String,Parcelable, orSerializable. Custom objects must implementParcelableor be reduced to a simple representation. - ViewModel – handles complex data structures without parceling, survives process death, and can be shared across composables. Introduces architecture overhead and a slightly larger memory footprint.
Concrete implementation
Below is a minimal Counter composable that demonstrates each option. Replace the // choose one block with the variant you want to test.
@Composable
fun CounterScreen() {
// choose one:
// 1️⃣ remember – transient only
// var count by remember { mutableStateOf(0) }
// 2️⃣ rememberSaveable – survives rotation if Int is parcelable (it is)
// var count by rememberSaveable { mutableStateOf(0) }
// 3️⃣ ViewModel with SavedStateHandle
// val viewModel: CounterViewModel = viewModel()
// val count by viewModel.count.collectAsState()
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Count: $count")
Button(onClick = { count++ }) { Text("Increment") }
}
}
@HiltViewModel
class CounterViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val count: MutableStateFlow = savedStateHandle.getStateFlow("count", 0)
fun increment() { count.value = count.value + 1 }
}
Place the composable in an activity that uses ComponentActivity with Compose setContent. No extra permissions are required; the code runs on any API 21+ device or emulator.
Validation steps
- Test configuration‑change survival: Run the app, tap Increment a few times, then rotate the device or emulator. Observe whether the count remains unchanged.
- Test process‑death survival: After setting a count, background the app, open the recent‑apps view, and swipe the app away to kill its process. Re‑launch the app from the launcher. The count should be restored only if you used
rememberSaveable(with a parcelable type) orViewModel. - Check for parcelable limits: Replace
Intwith a custom data class that does notParcelable. TherememberSaveableversion will fail to compile, while theViewModelversion will still work.
If the count is lost after rotation when using remember, that confirms the expected transient behavior. If the count disappears after a process kill when using rememberSaveable with a non‑parcelable type, you have hit the parcelable limitation.
Limitations and practical checks
rememberSaveablecannot store complex object graphs directly; you must either make themParcelableor convert them to a simple representation (e.g., an ID).- Over‑using
ViewModel for UI‑only state can cause unnecessary memory retention; preferrememberSaveable for UI‑confined data to keep the ViewModel layer focused on business logic. - When using Hilt or manual ViewModel factories, ensure the ViewModel scope matches the intended lifecycle (e.g.,
navGraphViewModelfor Navigation‑graph scoping).
To verify the result in practice, follow the validation steps above on a physical device or emulator. No special tools are required beyond Android Studio’s run‑deploy cycle.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.