← All shorts
21Compose 1.0+2 min read

derivedStateOf: Stop Recomposing on Every Scroll Pixel

Derive booleans from fast-changing state so composables only recompose when the derived value actually changes.

A scroll offset changes on every drag delta. Whether a "scroll to top" button should be visible changes maybe twice in a whole fling. Read the fast value directly in composition and you pay full recomposition price on every pixel, even though the UI only cares about the coarse yes-or-no answer you derived from it.

#The old way

@Composable
fun ScrollToTopButton(listState: LazyListState) {
    val showButton = listState.firstVisibleItemIndex > 0 ||
        listState.firstVisibleItemScrollOffset > 0

    if (showButton) {
        FloatingActionButton(onClick = { }) {
            Icon(Icons.Default.ArrowUpward, contentDescription = "Scroll to top")
        }
    }
}

firstVisibleItemIndex and firstVisibleItemScrollOffset both change on almost every scroll frame. ScrollToTopButton recomposes right along with them, whether or not the button's visibility flips.

#The new way

@Composable
fun ScrollToTopButton(listState: LazyListState) {
    val showButton by remember {
        derivedStateOf {
            listState.firstVisibleItemIndex > 0 ||
                listState.firstVisibleItemScrollOffset > 0
        }
    }

    if (showButton) {
        FloatingActionButton(onClick = { }) {
            Icon(Icons.Default.ArrowUpward, contentDescription = "Scroll to top")
        }
    }
}

#Why it matters

  • Recomposition is scoped to state reads; derivedStateOf collapses two high-frequency reads into one low-frequency boolean read.
  • Downstream composables (icons, animations tied to showButton) stop re-running every frame during a fling.
  • Composition stays cheap even on long lists with heavy scroll velocity.

#Gotcha

derivedStateOf adds its own snapshot-observation overhead. Derive offset * 2 from offset and the derived value changes exactly as often as the input, so you've bought a layer of indirection and saved nothing. Reach for it only when the derived value genuinely changes less often than the state it reads.

androidcomposeperformance

Related shorts