Loading...
Loading...
Use when Jetpack Compose code reads scroll, animation, gesture, or other frame-rate State in composition, passes changing values across composable boundaries, or uses value-form layout/draw modifiers.
npx skill4agent add chrisbanes/skills compose-state-deferred-readsState<T>State<T>val x by animate*AsState(...)Modifier.offset(x = ...)Modifier.size(...)Modifier.graphicsLayer(...)LazyListState.firstVisibleItemScrollOffsetScrollState.valueAnimatable.valuescrollOffset: Intprogress: FloatdragOffset: Offset// Before: animated value read in composition by the `by` delegate
@Composable
fun SelectionPill(selectedIndex: Int) {
val offsetX by animateDpAsState(120.dp * selectedIndex)
Box(Modifier.offset(x = offsetX))
}
// After: State is kept, value is read in the layout-phase offset block
@Composable
fun SelectionPill(selectedIndex: Int) {
val offsetX = animateDpAsState(120.dp * selectedIndex)
Box(
Modifier.offset {
IntOffset(offsetX.value.roundToPx(), 0)
},
)
}| Composition read | Deferred read |
|---|---|
| |
| |
| |
drawBehindState.value// Before: HomeScreen reads scroll offset in composition and passes the value down
@Composable
fun HomeScreen() {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
item { HeroImage(scrollOffset = listState.firstVisibleItemScrollOffset) }
}
}
@Composable
fun HeroImage(scrollOffset: Int, modifier: Modifier = Modifier) {
AsyncImage(
model = "...",
modifier = modifier.graphicsLayer(translationY = -scrollOffset / 2f),
)
}
// After: the only read happens inside graphicsLayer
@Composable
fun HomeScreen() {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
item {
HeroImage(
scrollOffsetProvider = {
if (listState.firstVisibleItemIndex == 0) {
listState.firstVisibleItemScrollOffset
} else {
0
}
},
)
}
}
}
@Composable
fun HeroImage(scrollOffsetProvider: () -> Int, modifier: Modifier = Modifier) {
AsyncImage(
model = "...",
modifier = modifier.graphicsLayer {
translationY = -scrollOffsetProvider() / 2f
},
)
}ProviderModifier.layout { measurable, constraints -> ... }Alignment.align(...)drawWithContentdrawBehindgraphicsLayer { ... }offset { ... }| Symptom | Diagnosis | Fix |
|---|---|---|
| | Keep |
| Property-argument form uses composition values | Use |
| Fast-changing value crosses boundary | |
| Draw block still recomposes every frame | Value was read before draw block | Move the |
| State chooses between different UI branches | Composition decision | Keep the read in composition |
compose-state-holder-ui-splitcompose-stability-diagnosticscompose-modifier-and-layout-stylemodifier