Android path · The screen and its data · Topic 6 of 12

ViewModel and state that survives

Not started

A ViewModel is a state holder for one screen that is kept in a store owned by the activity, fragment or navigation back-stack entry. When the activity is recreated for a configuration change, the new instance gets the same ViewModel back, with its state and running coroutines intact. When the owner is finished for good (the user navigates back), the ViewModel is cleared and viewModelScope is cancelled.

Why it matters: A ViewModel keeps screen state through configuration changes but not through process death. SavedStateHandle and disk cover the rest, and choosing the right one per value is most of the job.

  1. 1Scoped to a screen

    A ViewModel lives as long as its owner (activity, fragment or navigation entry) is not finished, across any number of recreations.

  2. 2Not across process death

    When the process is killed the ViewModel is gone. Only saved state and disk come back.

  3. 3SavedStateHandle for the small stuff

    Ids, queries and filters go in SavedStateHandle; the ViewModel reloads the rest from them.

1

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

ValuePut it in
Scroll position, expanded/collapsed, text field being typed (UI element state)rememberSaveable in the composable
Screen inputs: query, filter, selected idSavedStateHandle
Loaded data: lists, detailsRepository (Room, network), observed by the ViewModel
Drafts the user would hate to loseDisk, 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.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.The user fills in half a form, switches apps, and the process is killed. Which survives?
  2. 2.Why shouldn't a ViewModel hold a reference to an Activity or a View?
  3. 3.What should go into SavedStateHandle for a search screen showing 200 results?

0 of 3 right so far. Get all three to finish this topic.