Sitemap

Level Up Coding

Coding tutorials and news. The developer homepage gitconnected.com && skilled.dev && levelup.dev

Tricky Android Interview Questions: ViewModel & State Handling Edition

5 min readJun 9, 2025

--

Press enter or click to view image in full size

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 stateIn creates a hot flow
  • Understand that default StateFlow behavior differs from LiveData
  • Can control when your flow starts and stops — especially for expensive upstream sources (network, DB)

What to remember

  • LiveData starts/stops automatically — StateFlow doesn't, unless you use SharingStarted.WhileSubscribed()
  • stateIn(..., Eagerly) → always running
  • stateIn(..., Lazily) → starts once, never stops
  • stateIn(..., 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 the ViewModel
  • Use collectWithLifecycle() in the Composable

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:

  • rememberSaveable stores data in the UI layer — tied to a composable’s lifecycle and state restoration.
  • SavedStateHandle works inside the ViewModel — available even before the UI is composed.

Both can restore after process death (when properly configured), but:

  • rememberSaveable depends on Compose’s state restoration and component recreation.
  • SavedStateHandle integrates with SavedStateRegistry at 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 composition
  • SavedStateHandle : 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

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.

Press enter or click to view image in full size

Senior Android Developer
Writing honest, real-world Kotlin & Jetpack Compose insights.
📬 Follow me on Medium

--

--

Level Up Coding
Level Up Coding
Anatolii Frolov
Anatolii Frolov

Written by Anatolii Frolov

Android & Kotlin insights — practical tips, interview prep, no fluff.

Responses (4)

Thanks for another solid article. This whole series has been a great read.

--

Really appreciated the clarity around stateIn and how it differs from LiveData’s lifecycle handling. The section on SharingStarted.WhileSubscribed() was especially useful — something that often trips devs up!

--

There might be an error in your article about `rememberSaveable`. I'm pretty sure it also survives process death just like `SavedStateHandle` as the official documentation suggests. If it's not the case could you please provide sources, where did…

--