Jetpack Compose Effect Handlers
How to write lifecycle-aware side effects in Compose
What is a side effect?
A side effect is Anything that escapes the scope of the function where it is called. Here is an effect to keep some external state updated:
It initializes and updates a callback reference on an external object as a side effect of the composition. We don’t have control over when the effect runs (Composable functions can run multiple times), and it is never disposed.
We need to ensure that
Effects run on the correct lifecycle step
Effects can dispose resources when leaving composition
Suspend effects are cancelled when leaving composition
Effects that depend on an input that varies over time are automatically disposed/cancelled & relaunched every time it varies
These mechanisms are provided by the Effect Handlers.
Composables enter the composition when attached and leave it when detached. Between both events, effects run. Some effects can outlive this cycle and span across recompositions.
For an effect that requires cleanup before leaving.
Fired when entering and every time keys change
Disposed when leaving, and every time keys change (before re-trigger)
This handler attaches a callback to an external dispatcher when entering and disposes it when leaving. The dispatcher is used as a key to ensure dispose+restart if it changes. For spanning the same effect across recompositions instead of retriggering it you can pass a constant key:
DisposableEffect always requires at least 1 key.
“Fire on this composition or forget”. If the composition fails, it gets discarded. Used for notifying state updates to objects not managed by Compose.
For effects that do not require dispose
Runs after every successful composition/recomposition
Publishing updates to external states not managed by Compose
Not an effect handler, but interesting. Same than
invalidate. Can be used from any Composable function to invalidate the composition locally 👉 enforce recomposition.
⚠️ Anti-pattern. Rely on Snapshot state to drive recomposition.
Can be useful when using a source of truth that is not compose State. This presenter does not rely on Compose
State, invalidation is done manually:
To derive some state from other state objects.
Useful when we have some mutable state and other states that need to be recalculated only when the original changes
An optimization: Avoids recalculating on every recomposition
To create jobs that are considered children of the composition.
Runs suspended effects bound to the composition lifecycle
Scope cancelled when leaving the composition
Same scope across recompositions: all submited jobs cancelled when leaving
Useful to launch jobs in response to user interactions
Throttling on UI. Alike
postDelayed for Views. Every time the input changes it cancels the previous job and posts a new one with a delay.
Similar but for loading initial state when entering the composition instead of after user interaction.
Runs suspend effect when entering, cancels it when leaving
Cancels and relaunches the effect when key/s change/s
Also useful to span a job across recompositions
Syntax sugar on top of
LaunchedEffect feeds a
Supports default value. Behaves the same as
To capture a value in an effect & update it later without triggering it.
Useful for long lived operations that would be costly to recreate and restart
Useful when effect depends on a value that might change over time but we still want the effect to span across recompositions
Every time it is called it will update its value but will not retrigger effects reading from it.
3rd party library adapters
There are adapters for multiple well known libraries that delegate to the effect handlers.
You can find documentation for all the adapters available to convert 3rd party data types to Compose State here. There is also a collectAsStateWithLifecycle variant for collecting flows safely in Android. Finally, there is an adapter to convert from Compose Snapshot State to a cold Flow here.
All these adapters rely on effect handlers like
produceState to observe new values emitted from the original source and map them into a Compose State instance.
Consider becoming a free or paid subscriber.