Collecting safely from the UI

Coroutines Mid-level Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Pick the fix

Location updates keep flowing while the app is in the background. What fixes it?

class MapActivity : ComponentActivity() {
    private val vm: MapViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launch {
            vm.locations.collect { render(it) }
        }
    }
}
Options
Hint 1 of 1

Which lifecycle state means the screen is visible?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Wrap the collect in repeatOnLifecycle(Lifecycle.State.STARTED)

Correct. Collection stops in the background and restarts when the screen is visible.

Why not the others?
  • B Use GlobalScope.launch instead. That never stops, even after the Activity is destroyed.
  • C Add .flowOn(Dispatchers.IO). That changes the producer's thread, not when it runs.
  • D Call collect inside onResume. Each resume would start another collector and none would stop.

30-second answer

lifecycleScope.launch { flow.collect { } } keeps collecting while the app is in the background, wasting work and possibly updating views that aren't shown. repeatOnLifecycle(Lifecycle.State.STARTED) starts collection when the screen is visible and cancels it when it stops, then restarts it on return. In Compose, collectAsStateWithLifecycle does the same.

Practise saying it · 35 s. No recording, just a timer

Explain how to collect a Flow safely in the UI.

Say your answer out loud, then check which of these points you covered:

  • Plain lifecycleScope collects in the background
  • repeatOnLifecycle(STARTED) stops and restarts
  • viewLifecycleOwner in Fragments
  • collectAsStateWithLifecycle in Compose

Deep dive

The old launchWhenStarted only paused the coroutine; the upstream Flow kept producing. It is deprecated.

In a Fragment, use viewLifecycleOwner.lifecycleScope so collection follows the view, not the Fragment instance.

With a StateFlow, restarting collection replays the latest value, so nothing is missed.

Use flowWithLifecycle for a single flow; repeatOnLifecycle when you collect several in parallel.

The ViewModel used above Compile-checked · Kotlin 2.4.21
class MapViewModel : ViewModel() {
    val locations: Flow<String> = flowOf("52.5, 13.4")
}

Common mistakes

  • Collecting in lifecycleScope.launch without a lifecycle state, so it runs in the background.
  • Using the Fragment's lifecycle instead of viewLifecycleOwner.
  • Using collectAsState in Compose on Android when collectAsStateWithLifecycle exists.

Senior take & follow-ups

Explain the cost of background collection (battery, GPS, crashes from touching dead views) and give the one-liners for Views and Compose.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why not launchWhenStarted?

    Show hint

    It suspends the collector but keeps the producer running; it's deprecated.

  2. What happens to a SharedFlow event emitted while stopped?

    Show hint

    It's lost unless the flow buffers or replays it.

Suggest a fix if something here is wrong or out of date.