A child is skipped when its arguments are unchanged. Stable types (primitives, String, lambdas, classes of stable vals) are compared with equals(); unstable ones, such as a class with a var, are compared by instance under strong skipping (the default since Kotlin 2.0.20). On each of five taps StabilityDemo builds a new, equal Shelf("Staff picks", books) for ShelfBanner, and the same data as an @Immutable PinnedShelf for PinnedBanner:
D/BookNestState: StabilityDemo composed #1 D/BookNestState: ShelfBanner composed #1 D/BookNestState: PinnedBanner composed #1 D/BookNestState: StabilityDemo composed #2 ... D/BookNestState: StabilityDemo composed #6
Both banners were skipped every time. With composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") } in app/build.gradle.kts, after ./gradlew clean:
./gradlew compileReleaseKotlin
grep -A4 "demo.Shelf\|demo.PinnedShelf" app/build/compose_compiler/app-classes.txtruntime class com.example.booknest.demo.Shelf {
stable val name: String
runtime val books: List<Book>
<runtime stability> = Uncertain(List)
}
stable class com.example.booknest.demo.PinnedShelf {
stable val name: String
runtime val books: List<Book>
}Shelf is uncertain, not unstable, because List is an interface; the generated call passes no "unstable" bit in $changed, so ShelfBanner compared with equals() and skipped. So measure (these logs, or Layout Inspector's counts) rather than guess, and fix real cases by creating the object once, using ImmutableList from kotlinx.collections.immutable (https://github.com/Kotlin/kotlinx.collections.immutable 1,421 ), or an honest @Immutable.