Sharing Flows & Lifecycle-Safe Collection Quiz
KOTLIN › Flow
Two fragments collect the same cold flow exposed by a ViewModel. What happens?
- The producer runs twice, so the underlying query executes twice
- Both share a single upstream execution automatically
- The second collector only sees values emitted after it subscribed
- The second collect call throws, since cold flows allow one collector
Answer: The producer runs twice, so the underlying query executes twice
Cold means the producer belongs to the collector. Sharing one execution requires converting to hot with stateIn or shareIn.
Which sharing policy stops the upstream when nobody is listening?
- SharingStarted.WhileSubscribed(5_000)
- SharingStarted.Eagerly
- SharingStarted.Lazily
- All three, once the scope itself is cancelled
Answer: SharingStarted.WhileSubscribed(5_000)
Eagerly and Lazily both run until the scope dies, differing only in when they start. Only WhileSubscribed reacts to the subscriber count falling to zero.
Why is five seconds the conventional WhileSubscribed timeout?
- It outlasts a configuration change but not backgrounding
- It is the minimum value the API accepts
- It matches the ANR threshold for the main thread exactly
- It is how long a Room cursor stays valid
Answer: It outlasts a configuration change but not backgrounding
Rotation completes well inside it so the upstream survives and there is no re-query, while backgrounding exceeds it so real resources are released.
What does replayExpirationMillis control?
- How long the cached value survives after the upstream stops
- How long the upstream keeps running after the last subscriber leaves
- How many values the replay cache holds
- How long a new subscriber waits before receiving the cached value
Answer: How long the cached value survives after the upstream stops
stopTimeout is about the producer and replayExpiration is about the cache. The default keeps the cached value forever, so a returning screen renders last known state instantly.
What is wrong with this ViewModel code?
- Each call builds a new shared flow, so nothing is shared between callers
- Nothing: stateIn caches per scope automatically
- stateIn cannot be called outside a property initialiser
- The initial value is silently ignored when stateIn is called from a function
Answer: Each call builds a new shared flow, so nothing is shared between callers
Sharing needs one instance. As a function, every caller gets a fresh upstream and its own query, which is the situation stateIn was supposed to fix.
Why was launchWhenStarted deprecated?
- It suspends the collector instead of cancelling it, so the upstream keeps producing
- It runs the collector on a background dispatcher
- It fails to restart the collection when the lifecycle returns to STARTED once again
- It holds a strong reference to the lifecycle owner and leaks it
Answer: It suspends the collector instead of cancelling it, so the upstream keeps producing
A suspended collector still holds its subscription, so cursors stay open and WhileSubscribed never observes the subscriber count reaching zero.
The app is backgrounded. What happens to a collectAsState() subscription?
- It keeps collecting, because a backgrounded Activity does not leave composition
- It is cancelled along with the composition
- It is suspended until the app returns to the foreground
- It throws, since collecting anything below the STARTED state is simply not permitted
Answer: It keeps collecting, because a backgrounded Activity does not leave composition
That variant is tied to composition rather than lifecycle, and backgrounding does not end composition. It is the launchWhenStarted problem in Compose form.
In a Fragment, which scope should collect a flow that updates views?
- viewLifecycleOwner.lifecycleScope
- lifecycleScope
- viewModelScope
- GlobalScope, so the collection survives view recreation
Answer: viewLifecycleOwner.lifecycleScope
The Fragment scope lives until onDestroy while the view dies at onDestroyView, so using it renders into a released binding. The view lifecycle owner cancels at the right moment.
A ViewModel uses SharingStarted.Eagerly and the Fragment uses repeatOnLifecycle. Does the upstream stop when backgrounded?
- No: Eagerly runs the upstream regardless of subscribers
- Yes, because repeatOnLifecycle cancels the subscription
- Yes, after the default five second timeout
- No, because repeatOnLifecycle only suspends the collector
Answer: No: Eagerly runs the upstream regardless of subscribers
Both halves of the handshake are required. Cancelling the subscription only helps if the sharing policy reacts to subscriber count, which Eagerly does not.
How should a Fragment collect three separate flows?
- One repeatOnLifecycle block containing three launch calls
- Three separate repeatOnLifecycle blocks, one for each flow
- A single collect call combining all three with merge
- Three launchWhenStarted blocks to avoid restart cost
Answer: One repeatOnLifecycle block containing three launch calls
Each repeatOnLifecycle is its own coroutine with its own overhead, so one block with a launch per flow is the idiomatic shape.
Which converter should expose a stream of one-off navigation events?
- shareIn, since events have no meaningful current value
- stateIn, so that the latest event is always readable later
- stateIn with a null initial value
- Neither: expose the cold flow directly
Answer: shareIn, since events have no meaningful current value
stateIn requires a current value and conflates, which is wrong for events: a repeated event would be dropped and a stale one replayed. shareIn produces a SharedFlow instead.
What must you supply when calling collectAsStateWithLifecycle on a plain Flow?
- An initial value, since Compose must render something on the first frame
- A SharingStarted policy for the subscription
- A CoroutineScope for the collection
- Nothing at all: the initial value is inferred directly from the flow type
Answer: An initial value, since Compose must render something on the first frame
A StateFlow supplies its own current value but a bare Flow does not. That is a good reason to expose StateFlow from a ViewModel, so the initial value is decided once rather than per screen.
You want to convert a cold Flow into a hot stream that always holds a current value and replays it to every new collector while sharing a single upstream collection. Which operator do you use?
- shareIn
- buffer
- stateIn
- produceIn
Answer: stateIn
stateIn turns a cold flow into a StateFlow that always has a current value and emits it to new subscribers. shareIn produces a SharedFlow without an always-present value, and buffer does not make the flow hot at all.
In a View-based Activity, what is wrong with collecting this way, with no further wrapping?
- Collection keeps running in the background, wasting work on a stopped UI
- It will not compile because collect is not a suspend function to call there
- StateFlow cannot be collected from lifecycleScope at all, only elsewhere
- It automatically stops at STOPPED, so the plain launch is already enough
Answer: Collection keeps running in the background, wasting work on a stopped UI
Plain lifecycleScope collection runs until the scope is destroyed, so it continues while the UI is stopped. Wrapping in repeatOnLifecycle(Lifecycle.State.STARTED) cancels and restarts collection with visibility.
In Jetpack Compose, what is the recommended way to consume a ViewModel StateFlow as UI state?
- vm.state.collectAsState() inside a LaunchedEffect(Unit) block
- vm.state.value read directly from the composable body on every recomposition
- vm.state.collectAsStateWithLifecycle(), collects only while STARTED
- observeAsState() from the LiveData interop API, since it works for StateFlow
Answer: vm.state.collectAsStateWithLifecycle(), collects only while STARTED
collectAsStateWithLifecycle() (from lifecycle-runtime-compose) ties collection to the lifecycle, collecting only while at least STARTED and stopping otherwise, unlike plain collectAsState() which keeps collecting in the background.
You convert a cold repository flow into UI state with stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), Loading). What does WhileSubscribed(5000) achieve?
- It limits each single emission to a maximum of 5000 individual items
- It keeps the upstream alive 5s after the last collector unsubscribes
- It delays the very first emission by a full 5 seconds after subscribing
- It replays the last 5000 emitted values to each brand-new collector
Answer: It keeps the upstream alive 5s after the last collector unsubscribes
WhileSubscribed(stopTimeoutMillis = 5000) keeps the upstream producer alive for 5s after the last subscriber unsubscribes, so a configuration change does not tear down and restart the producer.