Blog Infos
Author
Published
Topics
, , , ,
Author
Published

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 (onCreateonStart, …).

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 LifecycleOwnerComponentActivity (base for AppCompat) does this by holding a LifecycleRegistry and forwarding Activity callbacks as lifecycle eventsON_CREATEON_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 statecancels 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 onAttach to onDestroy)
  • View’s lifecycle (exists from onCreateView to onDestroyView)

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 repeatOnLifecycle from an owner (Activity/Fragment) or use flowWithLifecycle. Guidance comes from Android’s coroutines + lifecycle docs.
8) Under the hood quick hits
  • Who changes state? ComponentActivity forwards each platform callback (onStart, …) to its LifecycleRegistry via handleLifecycleEvent, which transitions the state and notifies observers.
  • Why observers don’t leak owners? Registry holds LifecycleOwner in 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 lifecycleScope in a Fragment for view work—switch to viewLifecycleOwner.lifecycleScope and repeatOnLifecycle(STARTED).
  • Confused by flowWithLifecycle vs repeatOnLifecycle? Prefer flowWithLifecycle for a single flow; prefer repeatOnLifecycle when you have multiple collectors inside one block or need fine control. Official docs show both patterns.
  • @OnLifecycleEvent deprecation warnings: Replace with DefaultLifecycleObserver / 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

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

State Machines and Hopeful Dreams

Building out an Architecture for your application can be quite a difficult task. Thankfully (or Unfortunately) there are many solutions out there trying to solve this problem, acting as Architecture containers that create an opinionated…
Watch Video

State Machines and Hopeful Dreams

Rikin Marfatia
Android Engineer
Pinterest

State Machines and Hopeful Dreams

Rikin Marfatia
Android Engineer
Pinterest

State Machines and Hopeful Dreams

Rikin Marfatia
Android Engineer
Pinterest

Jobs

TL;DR
  • Treat lifecycle as data (states), not a tangle of callbacks.
  • Use repeatOnLifecycle(STARTED) or flowWithLifecycle 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

Menu