Layout Inspector's recomposition columns need Studio, but a composable can count itself: remember a counter and bump it in a SideEffect, which runs once after every composition that completes:
@Composable
fun LogCompositions(tag: String) {
if (!BuildConfig.DEBUG) return
val count = remember { intArrayOf(0) } // survives recomposition, not a State
SideEffect { // runs after every successful composition
count[0]++
Log.d("BookNestRecompose", "$tag: composition #${count[0]}")
}
}With a call in CatalogRoute and in each row (buildConfig = true generates BuildConfig), launching the app and typing "sal" into the search field logged:
D/BookNestRecompose( 2046): CatalogRoute query='': composition #1 D/BookNestRecompose( 2046): row 6: composition #1 ... D/BookNestRecompose( 2046): CatalogRoute query='s': composition #2 D/BookNestRecompose( 2046): CatalogRoute query='sa': composition #3 D/BookNestRecompose( 2046): CatalogRoute query='sal': composition #4
Each keystroke recomposed the screen but no row: unchanged rows were skipped. The Quiet Harbor, row 1, never composed at all, because it sat below the fold and LazyColumn composes only visible items. Strong Skipping explains when skipping fails.
A frame that misses its 16.7 ms deadline (at 60 Hz) is jank. adb shell dumpsys gfxinfo com.example.booknest reports frames since the last reset; here after six flings through the 600-book catalog of Lists with LazyColumn:
Total frames rendered: 34 Janky frames: 33 (97.06%) 50th percentile: 109ms ... Number Slow UI thread: 30
That indicts the software-rendered emulator on four shared CPUs more than the app. Measure jank on a real phone with a release build, using Scroll Jank's FrameTimingMetric.