Sparround

ViewModel, LiveData vs StateFlow, lifecycle-aware components

The ViewModel owns UI state and hosts UI-adjacent business logic. Key properties:

  • Survives configuration changes (via the ViewModelStore mechanism)
  • viewModelScope — a coroutine scope auto-cancelled in onCleared
  • Must hold no View or Activity-context references (leaks)
  • Process-death-resilient state via SavedStateHandle

LiveData vs StateFlow — the migration question appears in every interview:

  • LiveData: lifecycle-aware automatically (delivers at STARTED), Android-bound, poor operators.
  • StateFlow: rich Flow operators, KMP-friendly, usable in domain/data layers; lifecycle-awareness needs manual repeatOnLifecycle.

Lifecycle-aware components (DefaultLifecycleObserver) — the component itself subscribes to the lifecycle and manages its own start/stop; instead of bloating the Activity's onStart/onStop, the observer knows what to do.

kotlin
// Bank tətbiqində avtomatik logout taymeri
class SessionTimeoutObserver(
    private val onTimeout: () -> Unit
) : DefaultLifecycleObserver {

    private var job: Job? = null

    override fun onStart(owner: LifecycleOwner) {
        job = owner.lifecycleScope.launch {
            delay(5 * 60_000)       // 5 dəqiqə hərəkətsizlik
            onTimeout()
        }
    }

    override fun onStop(owner: LifecycleOwner) {
        job?.cancel()
    }
}

// Activity-də bir sətir:
lifecycle.addObserver(SessionTimeoutObserver { navigateToLock() })

A lifecycle-aware component: a session timer managing itself

How the ViewModel survives — they ask for the mechanism: on recreation, the ViewModelStore is passed to the new activity instance via NonConfigurationInstances; the ViewModelProvider finds and returns the existing ViewModel by key. So it is not magic — the store is carried over. onCleared fires only when the user truly leaves the screen (finish, back).

🛠 Practice task

Write a ViewModel with a StateFlow<UiState> + SavedStateHandle.

  • Rotate the screen — state survives; kill the process with "Don't keep activities" — the SavedStateHandle field survives, the rest does not. Compare the two.
  • Collect without repeatOnLifecycle(STARTED), background the app and watch the logs keep running; then fix it.
  • Write a DefaultLifecycleObserver (e.g. a simple session timer) and attach it via lifecycle.addObserver.

Done when: you can state the boundary between all three mechanisms with an example.

📚 Sources and documentation