From Activity callbacks to a state machine with coroutine-safe APIs.

1) The mental shift: callbacks → states
Classic lifecycle is a chain of callbacks on Activity/Fragment (onCreate, onStart, …).
AndroidX Lifecycle models lifecycle as a state machine that any LifecycleOwner exposes, with observers and coroutine-aware utilities for safe collection and cancellation.
State model (AndroidX)
INITIALIZED → CREATED → STARTED → RESUMED → (back down) → DESTROYED
Events drive transitions; state is the durable “where we are now.” Mappings to classic callbacks:


NOTE: No PAUSED state in AndroidX state
2) LifecycleOwner, Lifecycle, and LifecycleRegistry (internals)
Any component that exposes a lifecycle implements LifecycleOwner. ComponentActivity (base for AppCompat) does this by holding a LifecycleRegistry and forwarding Activity callbacks as lifecycle events: ON_CREATE, ON_START, … which move the registry’s state.
Under the hood: LifecycleRegistry stores current State, an owner WeakReference, and an observer map; handleLifecycleEvent(event) updates state and dispatches to observers in order.
Why WeakReference?
=> To avoid leaking the owner when observers outlive it.
3) Observing lifecycle: from annotations to DefaultLifecycleObserver
Old style:
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStart() { /*...*/ }
@OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStop() { /*...*/ }
}
lifecycle.addObserver(MyObserver())
@OnLifecycleEvent is deprecated. Use DefaultLifecycleObserver (Java 8 default methods) or LifecycleEventObserver (single callback) instead.
class MyObserver : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) { /*...*/ }
override fun onStop(owner: LifecycleOwner) { /*...*/ }
}
Rationale for deprecation & replacements: official release notes.
4) Coroutines + Lifecycle: the two golden tools
A) repeatOnLifecycle(state)
Runs your block when lifecycle is at least state; cancels it when dropping below; re-launches when back. Perfect for collecting Flows safely.
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.state.collect { render(it) }
}
}
Design background (why it relaunches; why earlier helpers were removed) by the API authors. (Medium)
B) flowWithLifecycle(lifecycle, state)
Operator that makes a Flow lifecycle-aware without writing repeatOnLifecycle boilerplate. Great for single flows. Internally uses a callbackFlow and adjusts upstream context, adding buffering semantics akin to flowOn.
viewLifecycleOwner.lifecycleScope.launch {
vm.state
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect(::render)
}
5) Fragment gotcha: lifecycle vs viewLifecycleOwner.lifecycle
Fragments have two lifecycles:
- Fragment’s lifecycle (exists from
onAttachtoonDestroy) - View’s lifecycle (exists from
onCreateViewtoonDestroyView)
For anything that touches views (binding, adapters), collect on viewLifecycleOwner.lifecycle and scope work in viewLifecycleOwner.lifecycleScope to avoid leaks when the view is destroyed but the Fragment remains.
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.ui.collect(::renderOnView)
}
}
6) Classic vs AndroidX: what actually changes in code?
Classic way (manual start/stop)
override fun onStart() {
super.onStart()
subscribe() // start DB/network listener
}
override fun onStop() {
unsubscribe() // must remember to cancel!
super.onStop()
}
AndroidX + KTX way (state-driven, cancellation for free)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
vm.updates.collect { render(it) } // auto-cancel/relaunch
}
}
This eliminates bookkeeping bugs and memory leaks; this is the officially recommended pattern for lifecycle-aware flows.
7) Compose integration (modern UI stack)
LaunchedEffect(key)launches a coroutine tied to composition; it cancels when the effect leaves composition.- For work that must respect owner lifecycle (e.g., foreground-only jobs), combine with
repeatOnLifecyclefrom an owner (Activity/Fragment) or useflowWithLifecycle. Guidance comes from Android’s coroutines + lifecycle docs.
8) Under the hood quick hits
- Who changes state?
ComponentActivityforwards each platform callback (onStart, …) to itsLifecycleRegistryviahandleLifecycleEvent, which transitions the state and notifies observers. - Why observers don’t leak owners? Registry holds
LifecycleOwnerin a WeakReference. - Why
DefaultLifecycleObserver? With Java 8 default methods, an interface can provide empty implementations, letting you override only what you need; also enables better tooling vs annotations. repeatOnLifecycle semantics: Suspend until destroyed; cancel & relaunch as you move below/above the threshold state. This pattern avoids stale collectors across rotations/navigation.
9) Troubleshooting & edge cases (battle-tested)
- Collectors firing after navigation / rotation? You likely used
lifecycleScopein a Fragment for view work—switch toviewLifecycleOwner.lifecycleScope andrepeatOnLifecycle(STARTED). - Confused by
flowWithLifecycle vsrepeatOnLifecycle? PreferflowWithLifecyclefor a single flow; preferrepeatOnLifecyclewhen you have multiple collectors inside one block or need fine control. Official docs show both patterns. @OnLifecycleEvent deprecation warnings: Replace withDefaultLifecycleObserver/LifecycleEventObserver. Check release notes for your Lifecycle version.
10) One-page cheatsheet
Use these defaults:
Activity/Fragment stateful collection:
lifecycleScope.launch { repeatOnLifecycle(STARTED) { flow.collect { … } } }
Fragment view-bound work:
viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(STARTED) { … } }
Single flow:
flow.flowWithLifecycle(lifecycle, STARTED).collect { … }
Observers: use DefaultLifecycleObserver (not @OnLifecycleEvent).
11) Bonus reading & a solid “internals” article
- Official Lifecycle docs & patterns. Android Developers
- API ref for
repeatOnLifecycle. Android Developers - Coroutines + lifecycle (Flows,
flowWithLifecycle) guide. Android Developers - Deprecation note for
@OnLifecycleEvent. Android Developers - Fragment View lifecycle docs (why
viewLifecycleOwner). Android Developers - A clear community write-up on Lifecycle basics &
LifecycleRegistry(useful background). rommansabbir.com flowWithLifecycledeep-dive by Android DevRel. Medium
Job Offers
TL;DR
- Treat lifecycle as data (states), not a tangle of callbacks.
- Use
repeatOnLifecycle(STARTED) orflowWithLifecycle for cancellation-safe Flows. - In Fragments, prefer
viewLifecycleOwner for anything that touches views. - Implement
DefaultLifecycleObserver, not@OnLifecycleEvent.
This article was previously published on proandroiddev.com



