L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Données et recherche · SKILL.md

validating-compose-stability

validating-compose-stability

Use this skill to gate CI on Jetpack Compose stability — catch when a composable becomes unskippable/unrestartable or a parameter goes stable → unstable, before it ships and tanks recomposition. Covers the Compose Stability Analyzer Gradle plugin's `stabilityDump` (write a `.stability` baseline) and `stabilityCheck` (compare current compilation against it, …

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: validating-compose-stability description: >- Use this skill to gate CI on Jetpack Compose stability — catch when a composable becomes unskippable/unrestartable or a parameter goes stable → unstable, before it ships and tanks recomposition. Covers the Compose Stability Analyzer Gradle plugin's stabilityDump (write a .stability baseline) and stabilityCheck (compare current compilation against it, fail on regression) tasks, the composeStabilityAnalyzer { stabilityValidation { … } } DSL (failOnStabilityChange, ignoreNonRegressiveChanges, ignored* lists, allowMissingBaseline, stabilityConfigurationFiles), the @IgnoreStabilityReport annotation, committing app/stability/app.stability as a team baseline, the deliberate baseline-update workflow, and GitHub Actions wiring (needs: build, since the analysis reads compiled output). Use when the user mentions "stabilityCheck", "stabilityDump", "compose stability analyzer", "stability baseline", "@IgnoreStabilityReport", "composeStabilityAnalyzer", or "fail the build on stability changes". license: Apache-2.0. See LICENSE for complete terms. metadata: author: Jaewoong Eum (skydoves) keywords:

  • jetpack-compose
  • compose-stability
  • recomposition
  • stability-analyzer
  • stabilityCheck
  • stabilityDump
  • stability-baseline
  • gradle-plugin
  • ci-cd
  • github-actions

Validating Compose Stability — A CI Gate On Skippability And Parameter Stability

Compose stability — whether a composable is skippable/restartable and whether its parameters are stable — is invisible until a recomposition profiler catches it in production. This skill makes it a build gate: snapshot the current stability of every composable into a committed .stability baseline, and fail CI when a change makes anything less stable. The tool is the Compose Stability Analyzer Gradle plugin. (Authoring composables for stability — @Immutable/@Stable, the compiler config file, deferred reads — is covered in the compose-performance-skills repo; this skill is purely the validation/CI layer.)

When to use this skill

  • The user wants ./gradlew stabilityCheck to fail CI when a composable becomes unskippable or a parameter flips stable → unstable.
  • The user wants a committed, reviewable baseline of every composable's stability (.stability files) so changes show up in PR diffs.
  • A recomposition regression shipped because nothing flagged a List parameter or a non-@Immutable data class added to a hot composable.
  • The user mentions stabilityDump, stabilityCheck, composeStabilityAnalyzer, @IgnoreStabilityReport, or "stability baseline".
  • The user wants warning-only locally and hard-fail in CI.

When NOT to use this skill

  • The user wants to fix an unstable parameter (@Immutable/@Stable, kotlinx.collections.immutable, stability_config.conf for external types, deferred state reads, derivedStateOf) — that is Compose performance authoring; see the compose-performance-skills repo's stability skills. This skill only validates and gates.
  • The user wants to find which recompositions are firing at runtime (Layout Inspector recomposition counts, Recomposer metrics, composition tracing) — different tool, runtime not build time.
  • The user is doing UI behavioral or screenshot testing — see ../../setup/choosing-test-rule-vs-runtest/SKILL.md and ../../preview/capturing-preview-screenshots-in-ci/SKILL.md.

Prerequisites

  • The Compose Stability Analyzer Gradle plugin applied to each Compose module (apply via the plugins block; coordinates and version from the project's catalog / the plugin docs).
  • Compose compiler metrics available — the plugin analyzes compiled output, so the module's Kotlin compilation must run before stabilityCheck/stabilityDump. In CI this means a needs: build (or running :module:compileDebugKotlin first) — running the check before compilation produces wrong results or fails.
  • A baseline committed to version control: <module>/stability/<module>.stability (generated by stabilityDump).

Workflow

  • 1. Configure the validation block. In each Compose module's build.gradle.kts:
composeStabilityAnalyzer {
    stabilityValidation {
        enabled.set(true)
        outputDir.set(layout.projectDirectory.dir("stability"))   // where the .stability baseline lives
        includeTests.set(false)
        ignoredPackages.set(listOf("com.example.internal"))
        ignoredClasses.set(listOf("PreviewComposables"))
        ignoredProjects.set(listOf("benchmarks", "examples"))
        failOnStabilityChange.set(true)            // build fails on stability changes; false => warning-only
        ignoreNonRegressiveChanges.set(false)      // true => only regressions count; new-stable / improvements ignored
        allowMissingBaseline.set(false)            // true => no baseline yet is not a failure (bootstrap only)
        stabilityConfigurationFiles.add(           // same format as the Compose compiler's stability config
            rootProject.layout.projectDirectory.file("stability_config.conf")
        )
    }
}

Key knobs: failOnStabilityChange (the gate), ignoreNonRegressiveChanges (report only regressions, ignore improvements and newly-added stable composables), stabilityConfigurationFiles (the Compose-compiler-format file declaring external types stable), and the ignored* lists for packages/classes/modules outside the contract.

  • 2. Generate the baseline once and commit it. With the module compiled:
./gradlew :app:compileDebugKotlin
./gradlew :app:stabilityDump          # writes app/stability/app.stability
git add app/stability/app.stability
git commit -m "Add Compose stability baseline"

The .stability file is human-readable: per composable it lists the fully-qualified signature, skippable/restartable status, and each parameter's stability classification with the reason. Android projects get variant-specific tasks (debugStabilityDump, releaseStabilityCheck, …); multi-module projects get one .stability file per module — ./gradlew stabilityDump runs them all, or target a module.

  • 3. Run the check in CI, after compilation. stabilityCheck compares the current compilation against the committed baseline and reports three change kinds: ~ stability regression (a composable/parameter became less stable), + a composable added, - a composable removed. On a regression the build fails with a message naming the affected composables and how their stability changed, and tells you to run stabilityDump if the change was intentional.
# .github/workflows/ci.yml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { java-version: '21', distribution: 'zulu' }
      - run: ./gradlew :app:compileDebugKotlin

  stability_check:
    name: Compose Stability Check
    runs-on: ubuntu-latest
    needs: build                       # MANDATORY — the analysis reads compiled output
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { java-version: '21', distribution: 'zulu' }
      - run: ./gradlew stabilityCheck
  • 4. Accept an intentional stability change deliberately. When a regression is real and accepted (a justified List parameter, an external type that genuinely can't be stable), update the baseline as a documented commit — never silently:
./gradlew :app:compileDebugKotlin
./gradlew :app:stabilityDump
git add app/stability/app.stability
git commit -m "Update stability baseline: PokemonList now takes List<Pokemon> — justified by [reason]"

The diff in the .stability file is the review artifact; the commit message is the audit trail.

  • 5. Exclude things that are not part of the contract. Annotate composables that should never be tracked — @Preview functions, internal scaffolding — with @IgnoreStabilityReport; they drop out of both the dump and the check. For whole packages/classes/modules, use ignoredPackages/ignoredClasses/ignoredProjects.
@IgnoreStabilityReport
@Preview
@Composable
fun UserCardPreview() { UserCard(user = User("John", 30)) }
  • 6. (Optional) strict in CI, warning-only locally. Gate hard on the CI server, stay non-blocking on a developer machine:
composeStabilityAnalyzer {
    stabilityValidation { failOnStabilityChange.set(System.getenv("CI") == "true") }
}

Most CI platforms set CI=true.

Patterns

Pattern: running stabilityCheck before compilation

# WRONG
stability_check:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - run: ./gradlew stabilityCheck            # no compiled output yet
# WRONG because: the analyzer reads the Compose compiler's output. With nothing compiled it
# either fails or reports incorrect results. The check must depend on a build step.
# RIGHT
stability_check:
  runs-on: ubuntu-latest
  needs: build                                  # or: - run: ./gradlew :app:compileDebugKotlin stabilityCheck
  steps:
    - uses: actions/checkout@v4
    - run: ./gradlew stabilityCheck

Pattern: regenerating the baseline to "make CI green"

# WRONG — CI failed on a stability regression, so:
./gradlew stabilityDump && git commit -am "fix stability check"
# WRONG because: stabilityDump rewrites the baseline to whatever the code currently produces, so
# the check trivially passes — the regression ships, undocumented. The dump is for *accepting* a
# change you understand, with a commit message that says why; it is not a way to silence the gate.
# RIGHT — investigate the ~ entries first; only then, if the change is justified:
./gradlew :app:compileDebugKotlin :app:stabilityDump
git add app/stability/app.stability
git commit -m "Update stability baseline: <composable> <what changed> — justified by <reason>"

Pattern: @Preview functions polluting the baseline

// WRONG — preview functions in the .stability baseline, churning it on every preview edit
@Preview @Composable fun ProfilePreview() { Profile(sampleUser) }
// WRONG because: previews are tooling entry points, not API. Tracking them means the baseline
// diffs every time someone tweaks a preview, drowning real regressions.
// RIGHT — annotate them out (or ignoredClasses = listOf("...Preview...") for a naming convention)
@IgnoreStabilityReport @Preview @Composable fun ProfilePreview() { Profile(sampleUser) }

Mandatory rules

  • MUST commit the generated .stability baseline files to version control — they are the shared contract; an uncommitted baseline gates nothing.
  • MUST run stabilityCheck only after Kotlin compilation (CI: needs: a build job, or compile in the same Gradle invocation). The analyzer consumes compiled output.
  • MUST treat a stabilityDump baseline update as a deliberate, reviewed commit with a justification in the message — never as a way to clear a failing check.
  • MUST exclude @Preview and other non-API composables from the contract via @IgnoreStabilityReport (or ignoredClasses/ignoredPackages) so the baseline diffs only on real changes.
  • MUST NOT set failOnStabilityChange = false (or allowMissingBaseline = true) permanently — those are for bootstrapping; once a baseline exists, the gate must fail on regressions.
  • MUST NOT use this skill as the place to fix instability — that is Compose authoring (@Immutable/@Stable, immutable collections, the compiler stability config); see the compose-performance-skills repo.
  • PREFERRED: ignoreNonRegressiveChanges = true if the team only cares about regressions and finds +/new-stable noise distracting.
  • PREFERRED: strict in CI, warning-only locally via failOnStabilityChange.set(System.getenv("CI") == "true").

Verification

  • ./gradlew stabilityDump produces <module>/stability/<module>.stability and it is committed (git status clean after a no-op dump).
  • ./gradlew stabilityCheck passes on the committed baseline and fails when a composable is deliberately made unstable (e.g. add a List<T> parameter to a tracked composable) with a message naming it and a ~ entry.
  • The CI workflow's stability job has needs: build (or compiles before stabilityCheck).
  • @Preview / scaffolding composables carry @IgnoreStabilityReport (or match an ignoredClasses pattern) and do not appear in the .stability file.
  • failOnStabilityChange is true (or CI-gated to true) and allowMissingBaseline is false once the baseline exists.

References

  • skydoves.github.io/compose-stability-analyzer/gradle-plugin/stability-validation/ — stabilityDump / stabilityCheck tasks, the composeStabilityAnalyzer { stabilityValidation { … } } DSL and every option, .stability baseline format, ~/+/- change types, @IgnoreStabilityReport, multi-module behavior.
  • skydoves.github.io/compose-stability-analyzer/gradle-plugin/ci-cd/ — GitHub Actions wiring, the mandatory needs: build dependency, build-failure behavior, the baseline-update workflow, failOnStabilityChange.set(System.getenv("CI") == "true").
  • github.com/skydoves/compose-stability-analyzer — the plugin source, coordinates, and full configuration reference.
  • developer.android.com/develop/ui/compose/performance/stability — Compose stability concepts (skippable/restartable, stable parameters) the baseline is built on.
  • developer.android.com/develop/ui/compose/performance/stability/fix — fixing instability (@Immutable/@Stable, immutable collections, the compiler stability_configuration_path file) — the authoring side this gate protects.
  • Sibling skill: ../../preview/capturing-preview-screenshots-in-ci/SKILL.md — another Compose CI gate (device-rendered preview catalog); same needs: build-style ordering concerns.
Skills enregistrées