compose-state-deferred-reads
Compose state deferred reads
Core principle
State reads invalidate the phase that reads them. If a State<T> is read in a composable body, changes invalidate composition. If it is read in layout or draw, changes can invalidate only layout or draw. Frame-rate state such as scroll offsets, animations, and drag positions usually belongs in layout/draw, not composition.
Back-writing is the symmetric failure mode: writing observable state from a phase that triggers invalidation of an earlier phase. Compose phases run composition → layout → draw. Writing snapshot-backed state from layout or draw to state read in composition invalidates composition; writing during composition to state read earlier in the same composition does the same. Both schedule extra work — often cascading into sibling lazy items.
The fix is structural: keep the State<T> or a provider lambda and read the value inside a layout/draw callback; capture measurements in callbacks and apply them in the measure phase, not by reading measurement state in sibling composable bodies.
When to use this skill
val x by animate*AsState(...)is passed toModifier.offset(x = ...),Modifier.size(...),Modifier.graphicsLayer(...), or another value-form modifier.LazyListState.firstVisibleItemScrollOffset,ScrollState.value,Animatable.value, or gesture state is read in a composable body.- A composable takes
scrollOffset: Int,progress: Float,dragOffset: Offset, or similar frame-rate values. - Recomposition counters climb during scroll, animation, or gestures even when data is stable.
- A composable body calls
stateMap[key] = …,list.addAll(…), or similar on every recomposition (back-writing composition → composition). - One lazy item captures size with
onSizeChanged/onGloballyPositionedand a sibling reads that height in composition (Modifier.height(state.dp)) — back-writing layout → composition.