collecting-flows-safely
Installation
SKILL.md
Collecting Flows Safely — keep upstream work tied to the UI lifecycle
collectAsState() keeps collecting whenever the composable is in composition, including while the app is backgrounded — that wastes CPU and battery. collectAsStateWithLifecycle() ties collection to a Lifecycle.State (default STARTED) so backgrounding pauses upstream work. For chatty flows, pair the consumer with .conflate() and .distinctUntilChanged() so Compose only sees meaningful changes. Skydoves hot take #4: a Flow<T> parameter on a composable is unstable and blocks skipping for the whole composable — collect at the caller and pass the resolved value.
When to use this skill
- A
ViewModelorRepositoryexposesStateFlow<T>,SharedFlow<T>, or a coldFlow<T>to a Compose screen. - The user reports background battery drain, "the screen keeps working when the app is in the recents tray", or wakelock noise on logcat.
- A composable consumes sensor data, location, GPS, websocket, or animation frames coming from outside Compose.
- A composable's signature is
fun MyScreen(state: Flow<State>)(Flow-as-parameter antipattern). - The user asks about
collectAsStatevscollectAsStateWithLifecycle, or aboutLifecycle.State.STARTED/RESUMEDsemantics. - The user wants Compose state to drive a non-Compose subscriber (analytics on visible item index, etc.) — that is the State→Flow direction served by
snapshotFlow.
When NOT to use this skill
- The flow is constructed and consumed entirely inside one composable's
remember { ... }block — that is composition-internal state, prefermutableStateOfdirectly. See../using-efficient-effects/SKILL.mdfor choosing the right effect API. - The data source is already
State<T>(e.g.mutableStateOf,Animatable.asState()) — do not wrap it in a flow just to callcollectAsStateWithLifecycle(). - A truly always-on background listener that must run while the Activity is
STOPPED. Move that work to aService/WorkManager/repeatOnLifecyclein the Activity, not into Compose.