100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Kotlin

Lifecycle-Aware Coroutines

Lifecycle-aware coroutine APIs like repeatOnLifecycle and lifecycleScope tie coroutine execution to Android component lifecycle states, avoiding wasted work and crashes from stale UI updates.

Coroutines & FlowAdvanced9 min readJul 8, 2026
Analogies

Lifecycle-Aware Coroutines

Android components like Activities and Fragments go through lifecycle states (CREATED, STARTED, RESUMED, and back down through PAUSED, STOPPED, DESTROYED), and collecting a Flow without respecting that lifecycle can cause real problems: a coroutine collecting a StateFlow while the Activity is STOPPED (e.g. the screen is off or another app is in the foreground) still does work and can even crash if it tries to update UI that isn't in a safe state. Lifecycle-aware coroutine APIs, provided by the androidx.lifecycle libraries, exist to tie coroutine execution directly to specific lifecycle states so that collection automatically pauses and resumes as the component moves through its lifecycle.

🏏

Cricket analogy: A ground announcer who keeps reading out live commentary even after play is suspended for bad light is doing pointless, risky work, similar to a coroutine collecting a StateFlow while the Activity is STOPPED and the screen isn't visible.

lifecycleScope is a CoroutineScope tied to a LifecycleOwner (an Activity or Fragment) that is automatically cancelled when the Lifecycle is destroyed — analogous to viewModelScope but bound to the UI component instead of the ViewModel. However, lifecycleScope.launch by itself does not pause collection when the component is merely stopped (only cancels on destroy), which is where repeatOnLifecycle comes in.

🏏

Cricket analogy: lifecycleScope is like a player's individual contract that's automatically terminated the moment they retire from the team (the Activity is destroyed), but merely being benched for a match (STOPPED) doesn't cancel it on its own — that's where repeatOnLifecycle comes in.

repeatOnLifecycle

repeatOnLifecycle(Lifecycle.State.STARTED) launches a new coroutine every time the Lifecycle reaches (or re-reaches) the STARTED state, and automatically cancels that coroutine when the Lifecycle falls below STARTED (i.e. becomes STOPPED). This means a Flow collected inside repeatOnLifecycle(STARTED) is actively collecting exactly while the UI is visible to the user, and stops collecting (freeing resources, pausing recomposition-triggering updates) the instant the screen is no longer visible — then automatically resumes collecting when the screen becomes visible again, without any manual bookkeeping.

🏏

Cricket analogy: repeatOnLifecycle(STARTED) is like fielders taking their positions the moment play resumes after a rain delay, and stepping off the field the instant bad light suspends play again — active exactly while the match is visible, automatically re-engaging when play restarts.

kotlin
class ProfileFragment : Fragment() {

    private val viewModel: ProfileViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    renderProfile(state)
                }
            }
        }
    }
}

// Compose equivalent: collectAsStateWithLifecycle wraps this pattern internally
@Composable
fun ProfileScreen(viewModel: ProfileViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle(
        minActiveState = Lifecycle.State.STARTED
    )
    ProfileContent(uiState)
}

collectAsStateWithLifecycle in Compose

In Compose, collectAsStateWithLifecycle is the lifecycle-aware equivalent for collecting a Flow directly into Compose State, and it wraps exactly the repeatOnLifecycle pattern internally — it automatically starts and stops collection based on the current Lifecycle.State (defaulting to STARTED) of the nearest LifecycleOwner, without requiring any manual repeatOnLifecycle boilerplate in the composable itself. This is why collectAsStateWithLifecycle is preferred over the plain collectAsState in production Compose code: collectAsState keeps collecting regardless of whether the app is in the foreground, continuing to do work (and potentially trigger recomposition) even while the app is backgrounded.

🏏

Cricket analogy: collectAsStateWithLifecycle is like a stadium's video board that automatically stops refreshing when the ground is closed to the public and resumes on match day, unlike a plain feed that keeps refreshing pointlessly even when no one's watching, wasting power.

Think of repeatOnLifecycle(STARTED) as an usher at a theater who only lets the show (Flow collection) run while there's an audience in the room (the screen is visible). The moment the room empties (STOPPED), the usher pauses the show; when the audience returns (STARTED again), the usher restarts it from the current state rather than leaving it running to an empty room the whole time.

Using plain collectAsState() (without the lifecycle-aware variant) in a production Compose screen is a subtle but real problem: collection continues even when the app is backgrounded, wasting battery/CPU and, in rare cases, attempting a StateFlow update from a background-only context. Always default to collectAsStateWithLifecycle() in app code; reserve plain collectAsState() for tests or non-Android common code where there is no Android Lifecycle to hook into.

  • Lifecycle-aware coroutines tie collection start/stop to specific Android Lifecycle states, avoiding wasted background work.
  • lifecycleScope is cancelled on Lifecycle destruction, analogous to viewModelScope but bound to the UI component.
  • repeatOnLifecycle(STARTED) restarts its block every time the Lifecycle re-enters STARTED and cancels it on STOPPED.
  • collectAsStateWithLifecycle wraps the same pattern for Compose, defaulting to STARTED as the minimum active state.
  • Plain collectAsState keeps collecting even when the app is backgrounded, unlike the lifecycle-aware variant.
  • Use viewLifecycleOwner (not the Fragment itself) when collecting Flows inside Fragment view code to match the view's shorter lifecycle.

Practice what you learned

Was this page helpful?

Topics covered

#Kotlin#AndroidWithJetpackComposeStudyNotes#MobileDevelopment#LifecycleAwareCoroutines#Lifecycle#Aware#Coroutines#RepeatOnLifecycle#StudyNotes#SkillVeris