kotlinx.collections.immutable

Activity, over time

Daily observations from the public event archive.

Attention

Daily stars & forks

Loading attention chart

Star accumulation

Cumulative stars

Loading star accumulation chart

Pull requests

Daily opens & merges

Loading pull requests chart

Conversations

Daily issues & comments

Loading conversations chart

Community

Daily accounts & reviews

Loading community chart

Releases

Daily releases

Loading releases chart

Mentions

1post · 30d
Nov 40 posts
  1. Android Compose Compiler Metrics in 2026 --- title: "Compose Compiler Metrics: Fix Recomposition Bugs Fast" published: true description: "Learn to parse Compose compiler reports, identify stability inference failures, fix unstable classes, and reclaim your 16ms frame budget." tags: android, kotlin, mobile, architecture canonical_url: https://mvpfactory.co/blog/compose-compiler-metrics-recomposition --- ## What You Will Build By the end of this tutorial you will have compiler-generated `.txt` metric files wired into your build, the vocabulary to read them, and a repeatable process for eliminating unnecessary recompositions before you ever open a profiler. In a mid-size e-commerce app with 87 composables and a heavy list-based UI, fixing three unstable data classes cut recompositions per scroll gesture from 340 down to 91 — a 73% reduction with zero business logic changes. ## Prerequisites - Kotlin Gradle Plugin 1.8+ - Compose Compiler 1.5+ - AGP 8+ --- ## Step 1: Enable the Compiler Report Add this to your `build.gradle.kts`. This is the minimal setup to get this working: tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach { compilerOptions { freeCompilerArgs.addAll( "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + project.layout.buildDirectory.get().asFile.absolutePath + "/compose_reports", "-P", "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + project.layout.buildDirectory.get().asFile.absolutePath + "/compose_metrics" ) } } After a build, check `build/compose_reports/` for four files: `*-classes.txt`, `*-composables.txt`, `*-composables.csv`, and `*-module.json`. --- ## Step 2: Read the Composables Report Open `*-composables.txt`. The compiler annotates every composable with its stability verdict: restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun ProductCard( stable product: Product stable onClick: Function0<Unit> ) restartable scheme("[androidx.compose.ui.UiComposable]") fun CartRow( unstable cartItem: CartItem stable onRemove: Function0<Unit> ) `CartRow` is restartable but **not skippable**. One unstable parameter blocks the entire composable. Every time the parent recomposes, `CartRow` follows — regardless of whether `cartItem` actually changed. Here is the pattern I use in every project to reason about this: | Status | Can Skip Unchanged Inputs | Recomposes on Parent Recompose | |---|---|---| | Restartable + Skippable | Yes | No | | Restartable only | No | Yes | | Not restartable | No | Inline in parent | Non-restartable is a **compiler decision**, not a developer target. Focus your work on moving composables from restartable-only into restartable-and-skippable. --- ## Step 3: Hunt Stability Inference Failures in `*-classes.txt` unstable class CartItem { stable val id: String stable val quantity: Int unstable val price: BigDecimal <runtime stability> = Unstable } `BigDecimal` is not on the Compose stability allowlist, so the entire class fails. The most common offenders in production are `BigDecimal`, `Date`, `Calendar`, and `java.util` collections (`List`, `Map`, `Set` — replace with `kotlinx.collections.immutable`). **If you own the class**, annotate it directly: @Immutable data class CartItem( val id: String, val quantity: Int, val price: BigDecimal ) **If you don't own it**, add it to a stability config file: // stability_config.conf com.squareup.moshi.JsonClass java.math.BigDecimal Reference it in Gradle: `stabilityConfigurationFile = rootProject.file("stability_config.conf")`. --- ## Step 4: Trace the Cascade The docs do not mention this explicitly, but one unstable class at the top of your tree can invalidate an entire subtree on every state write. Take a `CheckoutScreen` receiving an unstable `OrderSummary`. It hosts `PricingBreakdown`, `DeliveryOptions`, and `ConfirmButton` — none of which changed. Because `CheckoutScreen` is non-skippable, all three children recompose on every parent invalidation. If `OrderSummary` is rebuilt from a `StateFlow` on every emission, you are triggering that cascade on every keystroke in a nearby search field sharing the same composition scope. In a production app with 120 composables, I measured a single non-skippable screen-level composable generating over 300 unnecessary recompositions per second during a list scroll — 11ms of a 16ms frame budget, gone. The compiler report tells you *which parameter* is blocking skippability. The runtime profiler only shows you the symptom. Fix it at the source. --- ## Gotchas - **AGP 8+ deprecates `project.buildDir`** — use `layout.buildDirectory` as shown above, or the path will resolve incorrectly. - **`@Stable` is a promise, not a guarantee.** If you annotate a class `@Stable` but mutate it outside of Compose state, you will silently skip recompositions that should have happened. - **`kotlinx.collections.immutable` is not a drop-in `List` replacement.** You need to update call sites to `toPersistentList()` — easy to miss in ViewModels that return plain `listOf()`. - **Non-restartable composables are not your target.** Do not try to "achieve" non-restartable status. The compiler decides that for cheap, side-effect-free composables. Annotating toward it manually is a waste of effort. --- ## Three Things to Do Now 1. **Wire compiler reports into CI and diff `*-composables.txt` on PRs.** A new non-skippable composable in a feature branch should be a blocking comment, not a post-ship discovery. 2. **Audit every class that crosses a composable boundary.** Any `unstable` class in a screen-level parameter list is a guaranteed regression at scale. 3. **Set a recomposition budget per screen.** Target fewer than 5 recompositions per second on idle screens in Perfetto. If more than 20% of your composables are non-skippable, you have structural stability problems — no amount of `remember {}` will save you. I keep a recomposition budget checklist in my notes — right next to the HealthyDesk reminder that fires every 45 minutes telling me to stand up. Both exist because the cost of ignoring a small chronic drain is surprisingly high. **Further reading:** [Compose compiler metrics — official docs](https://developer.android.com/develop/ui/compose/performance/stability/diagnose) | [kotlinx.collections.immutable](https://github.com/Kotlin/kotlinx.collections.immutable)

Latest stargazers

The last 10 distinct accounts we observed this year.

Most active participants

PR, issue, comment & review events in the past year.

Packages

Scan pending.

Live mentions badge

Show how many posts mention your project in the last 30 days.

Live mentions · last 30 days
Kotlin/kotlinx.collections.immutable mentions in the last 30 days