Member-only story
Tricky Android Interview Questions: ViewModel & State Handling Edition
Other articles in this series
Tricky Android Interview Questions:
• Kotlin Coroutines Edition
• Flow & StateFlow Edition
• Jetpack Compose Effects Edition
• ViewModel & State Handling Edition ← you’re here
• Kotlin Object & Data Class Edition
This isn’t another “how to expose state from a ViewModel” article.
This is for those interview moments when the questions start simple — you explain how LiveData works, then Flow — and then comes the follow-up:
Can Flow actually behave like LiveData?
It’s a reasonable question. The code might look fine. But the behavior? That depends — unless you know exactly how stateIn, lifecycle, and sharing strategies interact.
This article focuses on subtle but important differences in how ViewModel state works — what survives configuration changes, what survives process death, and what doesn’t.
Question 1: Can you make a Flow behave like LiveData?
At first glance, it seems like a reasonable idea.
Both Flow and LiveData deliver observable data from a ViewModel to the UI.
So can you make a Flow behave just like LiveData — only collecting when there’s an active observer, and stopping when there isn’t?
Sort of. But not by default — and not without caveats.
Why this is a trick question
By default, Flow is cold and lazy. But once you turn it into a StateFlow using stateIn(), it becomes hot — and unless you configure it carefully, it may start collecting immediately and never stop.
LiveData starts and stops automatically based on active observers.StateFlow, on the other hand, keeps collecting as long as its scope is active — even if no one is reading its .value.
If you’re not careful, this can lead to background work continuing long after the UI is gone.
What actually happens
Let’s say you define this in a ViewModel:
val state = flow {
emit(loadData())
}.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = InitialValue
)This flow starts immediately — as soon as the ViewModel is created — even if no composable is on screen.
Now compare that to:
val state = flow {
emit(loadData())
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = InitialValue
)Here, the upstream flow starts only when someone collects, and stops after the subscriber disappears (with a short timeout).
This behaves much closer to LiveData.
Why this question exists
It tests whether you:
- Know that
stateIncreates a hot flow - Understand that default
StateFlowbehavior differs fromLiveData - Can control when your flow starts and stops — especially for expensive upstream sources (network, DB)
What to remember
LiveDatastarts/stops automatically —StateFlowdoesn't, unless you useSharingStarted.WhileSubscribed()stateIn(..., Eagerly)→ always runningstateIn(..., Lazily)→ starts once, never stopsstateIn(..., WhileSubscribed())→ starts/stops based on active collectors
Bonus insight: What if you use collectWithLifecycle()?
collectWithLifecycle() ensures your collector follows the UI lifecycle — for example, stopping when the composable leaves the screen.
But it does not control the upstream flow.
If you’re collecting from a StateFlow created with SharingStarted.Eagerly, the flow has already started.collectWithLifecycle() simply controls whether the UI receives updates — not whether the flow is running.
To truly mimic LiveData behavior:
- Use
stateIn(..., WhileSubscribed())in theViewModel - Use
collectWithLifecycle()in theComposable
That way, both producer and consumer are lifecycle-aware.
Question 2: Where rememberSaveable and SavedStateHandle actually differ
At first glance, both rememberSaveable and SavedStateHandle seem to solve the same problem: preserving state across configuration changes and process death.
They both can survive process death — but they work in different layers and have different scopes and guarantees.
That difference matters more than it seems.
Why this is a trick question
It’s tempting to treat rememberSaveable as a simpler alternative to SavedStateHandle.
Just pass a value into rememberSaveable, and it survives rotation. Why add extra code in the ViewModel?
But while the two APIs use similar underlying mechanisms (Bundle), they serve different roles:
rememberSaveablestores data in the UI layer — tied to a composable’s lifecycle and state restoration.SavedStateHandleworks inside theViewModel— available even before the UI is composed.
Both can restore after process death (when properly configured), but:
rememberSaveabledepends on Compose’s state restoration and component recreation.SavedStateHandleintegrates withSavedStateRegistryat the architecture level — and can be used outside composition.
What actually happens
Let’s say you store the selected tab index using rememberSaveable:
var tabIndex by rememberSaveable { mutableStateOf(0) }This survives configuration changes and process death — as long as the data is supported (e.g. primitive, String, or Parcelable).
But it only becomes available after the composable is recomposed. That may be too late for things like pre-selection or deep links.
Now compare with a ViewModel-based approach:
class MyViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val tabIndex: StateFlow<Int> = savedStateHandle.getStateFlow("tab_index", 0)
fun setTab(index: Int) {
savedStateHandle["tab_index"] = index
}
}And in Compose:
@Composable
fun TabScreen(viewModel: MyViewModel = viewModel()) {
val tabIndex by viewModel.tabIndex.collectAsState()
// UI that uses tabIndex
TabRow(selectedTabIndex = tabIndex) {
// ...
}
}This state is immediately available in the ViewModel — even before the UI is drawn — and is not tied to recomposition.
It also keeps logic in the ViewModel, where it belongs.
Why this question exists
It checks whether you:
- Understand the separation between UI and logic layers
- Know which solution provides earlier access and better testability
- Realize that both tools can survive process death — but only one works cleanly outside composition
What to remember
rememberSaveable: UI-only, survives process death for basic types, but only restores inside compositionSavedStateHandle: ViewModel-level, survives process death, available before UI is drawn, doesn’t depend on recomposition- For UI-only things (scroll, text), use
rememberSaveable - For logic, identity, and navigation state — use
SavedStateHandle
You might also like:
Enjoyed this article?
If it helped you, or clarified how ViewModel and state persistence actually work — consider leaving a clap. It helps others discover the article.
If you’re into Android interviews, architecture patterns, or practical edge cases — feel free to follow.
More tricky questions (and answers) are on the way.
Senior Android Developer
Writing honest, real-world Kotlin & Jetpack Compose insights.
📬 Follow me on Medium









