Choosing the Right Side Effect

We've now covered five APIs that all exist to solve the same underlying problem — letting a purely declarative composable reach out and touch the non-Compose world (coroutines, listeners, analytics SDKs, callback-based APIs) safely, without leaking work past the lifetime of the screen that started it. The hard part in practice isn't understanding any one of them in isolation, it's recognizing which one a given situation calls for. This lesson is a single reference table plus a set of RecipeBox scenarios to test that instinct.

The Decision Table

APIUse this when...
LaunchedEffect(key)you need to run suspend work tied to a composable's presence on screen, and want it to automatically re-run whenever a specific value (the key) changes.
DisposableEffect(key)you're registering something non-Compose (a listener, a receiver, an observer) that has an explicit "undo" step — onDispose { } guarantees that cleanup runs.
SideEffect { }you need to publish the latest Compose state out to a non-Compose object on every successful recomposition — no key, no cleanup, just "keep this in sync."
produceState(...)you have a callback-based or otherwise non-Compose async source and want its latest value exposed as ordinary Compose State<T> that any composable can read.
rememberCoroutineScope()you need to launch a coroutine from a non-composable context — a button's onClick, or any other event lambda — where you can't call LaunchedEffect directly.

A Quick Gut Check

One useful way to sort these: are you reacting to composition itself (a screen appearing, a value changing), or to a discrete event (a click)? The first four all run automatically as Compose recomposes; only rememberCoroutineScope hands you something to call on demand from outside that automatic flow.

RecipeBox Scenarios: Pick the Right Tool

ScenarioRight choice
RecipeBox's recipe list needs to load its data from the repository exactly once, the first time the list screen appears. LaunchedEffect(Unit)
The search screen needs to re-query recipes every time the user's search text changes, cancelling any still-running query for a stale term. LaunchedEffect(searchQuery)
The Add/Edit Recipe screen registers a location listener while visible, and must unregister it the moment the user navigates away. DisposableEffect(key) { onDispose { ... } }
Tapping "Save Recipe" needs to trigger both a repository save call and a Snackbar confirmation, from inside the button's onClick. rememberCoroutineScope(), then scope.launch { }
An analytics SDK's currentScreen property needs to always reflect whichever RecipeBox screen just finished recomposing. SideEffect { }
A legacy, callback-based geocoding library needs its latest result exposed as Compose State so a composable can read it with by. produceState(...)
If you're ever unsure: start by asking whether the work is suspend-based (leans LaunchedEffect/rememberCoroutineScope) or callback/listener-based (leans DisposableEffect/produceState), then whether it's triggered by composition or by a discrete event.
Next up: Side effects handle "make something happen." animate*AsState starts the animations section of this track — making Compose UI move smoothly between states instead of jumping instantly.