← All articles
14 JUN 2026 · 5 MIN READ

Jetpack Compose Side Effects, Explained

Compose's declarative model is great until your UI needs to do something outside of composition — start a coroutine, register a listener, or run cleanup. That's where side-effect APIs come in, and picking the wrong one is a common source of bugs and leaks.

LaunchedEffect

Use this when you need to run suspend work tied to a composable's lifecycle. It launches a coroutine when it enters composition and cancels it when it leaves, or when its keys change.

LaunchedEffect(userId) {
    viewModel.loadUser(userId)
}

The key (userId here) controls re-execution — change it, and the old coroutine cancels in favor of a new one.

DisposableEffect

Use this for non-suspend setup that needs explicit cleanup, like registering a broadcast receiver or a sensor listener.

DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event -> ... }
    lifecycleOwner.lifecycle.addObserver(observer)
    onDispose {
        lifecycleOwner.lifecycle.removeObserver(observer)
    }
}

The onDispose block is mandatory — forgetting it is the most common way to leak listeners in Compose screens.

rememberCoroutineScope

Use this when you need to launch a coroutine from an event callback (like a button click) rather than from composition itself.

val scope = rememberCoroutineScope()

Button(onClick = {
    scope.launch { viewModel.submit() }
}) { Text("Submit") }

Unlike LaunchedEffect, this scope is tied to the composable's lifecycle but doesn't auto-launch — you control exactly when work starts.

Quick rule of thumb


Written by Subham Bikash Behera — Senior Android Developer