How it works
The explanation, with examples you can run.
What it is for
class OrdersViewModel( private val repo: OrdersRepository, private val saved: SavedStateHandle,) : ViewModel() { val filter: StateFlow<String> = saved.getStateFlow("filter", "all") val orders: StateFlow<List<Order>> = filter .flatMapLatest { repo.observe(it) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) fun setFilter(value: String) { saved["filter"] = value }} @Composablefun OrdersScreen(vm: OrdersViewModel = viewModel()) { val orders by vm.orders.collectAsStateWithLifecycle() OrderList(orders)}The ViewModel exposes state as StateFlow, receives events as function calls, and talks to repositories. It must not hold an Activity, View or composable Context: it outlives them, so it would leak them. If it needs resources, inject what it needs or use AndroidViewModel with the application context.
With Hilt, @HiltViewModel and hiltViewModel() supply the constructor arguments; without DI, a ViewModelProvider.Factory (or the viewModelFactory { initializer { ... } } DSL) does, and createSavedStateHandle() gives you the handle.
The limit: process death
A ViewModel lives in memory. Once the system kills the process in the background, it is gone, and the user coming back gets a brand-new one. That is what SavedStateHandle is for: a key-value map that is written into the activity's saved state and given back to the new ViewModel after the process restarts.
val selectedId: String? get() = saved["selectedId"]fun select(id: String) { saved["selectedId"] = id }Values must be types a Bundle can hold (primitives, strings, Parcelables). Recent versions of the lifecycle library also offer a saved property delegate that stores @Serializable classes via KotlinX Serialization; check that your lifecycle version has it before relying on it.
Choosing where a value lives
| Value | Put it in |
|---|---|
| Scroll position, expanded/collapsed, text field being typed (UI element state) | rememberSaveable in the composable |
| Screen inputs: query, filter, selected id | SavedStateHandle |
| Loaded data: lists, details | Repository (Room, network), observed by the ViewModel |
| Drafts the user would hate to lose | Disk, saved as they type |
Saved state is a small, serialised snapshot that travels through the system; large Bundles can fail with TransactionTooLargeException. Store ids and reload the data.
Testing the full path
Rotation shows that the ViewModel works. To check saved state, background the app, kill the process (adb shell am kill <package>) and reopen it from Recents. SavedStateHandle can also be created directly in unit tests with a map of initial values.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.