diagnosing-compose-stability について
Jetpack Compose のコンポーザブルがスキップされない理由を診断する Claude 向けスキルです。release ビルドのコンパイラレポートから不安定なパラメーターを特定し、別の修正スキルに渡す前に優先度を整理します。
主な機能
- 01release ビルドで Compose Compiler レポートを有効化
- 02コンポーザブルのスキップ可否とパラメーターの安定性を確認
- 03クラスレポートから不安定な型の原因を追跡
- 04ホットパスと実行時測定に基づく優先順位付け
- 05修正スキルに渡す問題リストの作成
活用シーン
- 01過剰な再コンポーズやフレーム落ちの調査
- 02コンポーザブルがスキップされない理由の特定
- 03性能重視 UI の安定性根拠のレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ skydoves/ compose-performance-skills --skill diagnosing-compose-stabilityインストール後、Claude Code で/diagnosing-compose-stabilityを入力して読み込まれたか確認します。
同梱ファイルreferences/reading-classes-txt.mdreferences/reading-composables-txt.md
詳細
Compose の安定性を診断する — まずコンパイラレポートを確認する
Compose はパラメーターを比較して、再コンポジションをスキップします。パラメーターが不安定な場合、スキップは無効になります。このスキルでは、どのパラメーターが不安定なのか、その理由を Claude が特定する方法を説明します。出力は、不安定な型とスキップできないコンポーザブルを優先順位順に並べたリストです。修正方法は ../stabilizing-compose-types/SKILL.md にあります。
このスキルを使う場合
- 開発者が「なぜこれが再コンポジションされるのですか?」と尋ねる場合、ジャンク、フレーム落ち、スクロールのカクつきを報告する場合。
@TraceRecompositionのログに、意味のある状態変化の回数を超える再コンポジション回数が表示される場合。- 開発者が Compose Compiler Reports、
classes.txt、composables.txt、composables.csv、module.json、または「スキップ不可」に言及する場合。 - 開発者が不安定なパラメーターの特定方法や、
List<Foo>、LocalDateTime、ドメイン型が安定しているかどうかを尋ねる場合。 - レビュアーが、パフォーマンスが重要なコンポーザブルをスキップできるという根拠を求める場合。
このスキルを使わない場合
- 不安定な型がすでにわかっている場合は、
../stabilizing-compose-types/SKILL.mdに進んでください。 - 症状が、誤ったフェーズでの状態読み取り(
Modifier.alpha(state.value))の場合は、../../recomposition/deferring-state-reads/SKILL.mdを使ってください。 - 症状が
derivedStateOfの誤用の場合は、../../recomposition/choosing-derivedstateof/SKILL.mdを使ってください。 - 開発者が一度限りの診断ではなく CI によるゲートを求める場合は、
../enforcing-stability-in-ci/SKILL.mdを使ってください。
前提条件
- Compose Compiler Gradle プラグイン
id("org.jetbrains.kotlin.plugin.compose")が適用された Kotlin 2.0.0+。2.0 より前のkotlinCompilerExtensionVersionを使う方法は廃止されています。 - 対象モジュールにビルド可能な release バリアントがあること。レポートは必ず release で生成してください。debug では Live Literals が追加され、定数が動的に見えるため、すべてのレポートに偏りが生じます。
org.jetbrains.kotlin.plugin.composeGradle プラグイン(Kotlin 2.0+)を適用してください。composeCompiler { … }拡張機能はこのプラグインが提供します。AGP のバージョンは関係ありません。- 任意ですが、推奨: 開発者が 100%スキップ可能にすることではなく、安定性のベースライン目標を持っていること(skydoves hot take #1 — スキップ可能かどうかは診断指標であり、KPI ではありません)。
ワークフロー
- 1. モジュールの
build.gradle.ktsでレポートを有効にします。 対象はリリースビルドのみにしてください。デバッグビルドでは誤解を招くデータが出力されます。
// app/build.gradle.kts (or any compose module)
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.compose")
}
composeCompiler {
// Only emit reports for release to avoid Live Literals noise.
val isReleaseBuild = providers.gradleProperty("composeCompilerReports").orNull == "true"
if (isReleaseBuild) {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
metricsDestination = layout.buildDirectory.dir("compose_compiler")
}
}
または、常に有効にする形式を使います。
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
metricsDestination = layout.buildDirectory.dir("compose_compiler")
// Optional — opt mutable third-party types into stability:
stabilityConfigurationFiles.add(
rootProject.layout.projectDirectory.file("stability_config.conf")
)
}
- 2. リリースバリアントをビルドします。 Live Literals とインタープリターモードのノイズが含まれないようにします。
./gradlew :app:assembleRelease -PcomposeCompilerReports=true
ライブラリモジュールの場合は、./gradlew :feature-feed:assembleRelease を実行します。リリースフレーバーが必要です。以下の「誤り/正解」を参照してください。
- 3. 4 つの出力ファイルを探します。
<module>/build/compose_compiler/に書き出されます。
app/build/compose_compiler/
├── app_release-classes.txt # per-class stability
├── app_release-composables.txt # per-composable signatures
├── app_release-composables.csv # CSV mirror of the above (CI-friendly)
└── app_release-module.json # aggregate counts
ファイルが見つからない場合、そのバリアントではプラグインが実行されていません。composeCompiler { reportsDestination = ... } が正しいモジュールに設定されていることと、リリースビルドであることを再確認してください。
-
4. 最初に
composables.txtを開きます。 これが最も有用なファイルです。skippableが後に続かないrestartableの行を検索してください。それぞれがスキップできない再コンポジションのエントリーポイントです。各ブロック内で、パラメーターごとのプレフィックスstable、unstable、@static、@dynamicを確認してください。unstableパラメーターがあるとスキップできません。文法の詳細はreferences/reading-composables-txt.mdを参照してください。 -
5.
classes.txtを開いて、クラスが不安定な 理由 を確認します。 composables.txt でunstableと判定されたすべての型について、classes.txt で宣言を探してください。その行には、varフィールド、ジェネリックパラメーター、不安定なネスト型のどれが原因なのかが示されています。runtime stable class Box { stable val value: T }という形式は、「置換された T のランタイム$stable: Intフィールドが安定を示す場合に限り、このクラスは安定である」という意味です。references/reading-classes-txt.mdを参照してください。 -
6. トリアージの数値を確認するため、
module.jsonを開きます。 スキップ可能なコンポーザブル、再起動可能なコンポーザブル、安定したクラスなどの数が記載されています。修正の前後を比較したり、どのモジュールから取り組むかを決めたりする際に使用してください。これらの数を目標として扱わないでください。 これらはリグレッションの検出に使うものであり、100 パーセントを目指すためのものではありません。 -
7. ホットパスを優先します。 起動時に一度だけ実行されるスキップ不可能なコンポーザブルは重要ではありませんが、
LazyColumnのアイテム内にあるものは重要です。修正前に計測結果(skydoves/compose-stability-analyzer の@TraceRecomposition、または Macrobenchmark のFrameTimingMetric)と照合してください。../../measurement/tracing-recompositions-at-runtime/SKILL.mdを参照してください。 -
8. 修正スキルに引き継ぎます。 不安定な型と問題のあるコンポーザブルの一覧を作成し、
../stabilizing-compose-types/SKILL.mdを適用してください。
パターン
パターン:HighlightedSnacks — スキップできないコンポーザブルを読む
コンパイラーのレポートには、関数シグネチャ内に原因が直接示されています。この実際の形式を使って、開発者に順を追って説明します。
restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
stable index: Int,
unstable snacks: List<Snack>, // <-- blocks skipping
stable onSnackClick: Function1<Long, Unit>,
)
診断手順:
- 関数は
restartableですが、前にskippableが付いていません。そのため、親が再コンポーズされると常に再コンポーズされます。 - 原因は
unstable snacks: List<Snack>パラメーターです。kotlin.collections.Listはインターフェースであり、コンパイラーはその実装が不変であることを証明できません。 classes.txtを開き、Snackを探します。Snack自体がunstableの場合は、先にデータクラスを修正します。stableの場合は、問題はListラッパーだけです。- 修正スキルに引き継ぎます。
List<Snack>をkotlinx.collections.immutable.ImmutableList<Snack>に置き換えるか、開発者がその契約に納得している場合はkotlin.collections.*をstability_config.confに追加します。
パターン:「stable but runtime」— runtime の意味
runtime stable class Box {
stable val value: T
}
これは不安定ではありません。コンパイラーは実行時に合成 $stable: Int フィールドを出力し、コンポジション中にその値を確認します。置換された T が安定を示す場合に限り、このクラスは安定です。runtime クラスを「昇格」させるために @Stable を付けてはいけません。ランタイムチェックが適切な仕組みです。
パターン:誤りと正解 — ビルドの実行
# WRONG
./gradlew :app:assembleDebug
# WRONG because: debug enables Live Literals; constant 0 dp becomes a getter, every literal looks dynamic, and counts in module.json drift versus what ships to users.
# RIGHT
./gradlew :app:assembleRelease -PcomposeCompilerReports=true
パターン:誤りと正解 — レポートファイルの読み方
// WRONG — reading composables.txt without checking the leading flags
fun MyScreen(...)
// WRONG because: skipping the `restartable`/`skippable` prefix discards the only data point that determines whether unstable params actually cost anything.
// RIGHT — every diagnosis quotes the full prefix and per-param annotations
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun MyScreen(
stable user: User,
stable onClick: Function0<Unit>,
)
パターン:出力ディレクトリが空
リリースビルド後に build/compose_compiler/ が存在しない、または空の場合:
org.jetbrains.kotlin.plugin.composeプラグインがこのモジュールに適用されていることを確認します。拡張機能はモジュールごとに設定されます。- ビルドで実際に Kotlin ソースがコンパイルされたことを確認します(最新状態のため何も実行されない場合を除きます)。ファイルを更新するか、
./gradlew :app:clean :app:assembleReleaseを実行します。 reportsDestinationが、従来のkotlinOptionsの freeCompilerArgs フローではなく、composeCompiler { }ブロック内に設定されていることを確認します。
必須ルール
- 必須: リリースバリアントをビルドします。デバッグビルドでは Live Literals が出力され、定数が動的に見えるため、レポートの「不安定」な範囲が膨らみます。
- 必須: 関数名だけでなく、
composables.txtのパラメーターごとの注釈(stable/unstable/@static/@dynamic)を読みます。フラグの接頭辞が診断内容です。 - 必須: 不安定なパラメーターを
classes.txtと照合し、根本原因(var、ジェネリック、不安定なフィールド型、またはインターフェース)を特定します。 - 禁止: スキップ可能性を 100 パーセントにしようとしないでください。Skydoves の見解その1:スキップ可能性は診断であり、KPI ではありません。起動時に一度だけ実行されるコンポーザブルは、スキップ可能である必要はありません。
- 禁止: レポートだけを根拠にクラスへ
@Stableを付けないでください。その判断は、契約を評価する修正スキルの役割です。 - 禁止:
composables.csvの行数を読んで完了としないでください。件数はリグレッションを検知する指標であり、実際の診断は行ごとの注釈に基づいて行います。 - 推奨: 同じフラグを
../enforcing-stability-in-ci/SKILL.md経由で CI に組み込み(skydoves のcompose-stability-analyzerプラグイン、またはコミュニティのComposeGuardプラグイン)、PR レビューでリグレッションが明らかになるようにします。 - 推奨: リリースごとにベースラインとなる
module.jsonを記録し、今後のリグレッションを差分で確認できるようにします。
検証
-
org.jetbrains.kotlin.plugin.composeプラグインを適用した状態で、./gradlew :app:assembleRelease(またはモジュール固有のコマンド)が正常に完了する。 - 4 つのファイルすべてが存在する:
<module>_release-classes.txt、<module>_release-composables.txt、<module>_release-composables.csv、<module>_release-module.json。 -
composables.txtからコピーした不安定なパラメーター箇所の一覧、または修正が不要であることを裏付けるmodule.jsonのゼロ件確認の、少なくとも一方がある。 - 各不安定なパラメーターを
classes.txt内のクラスまで追跡し、修正スキルが具体的な根本原因(var、List、LocalDateTimeなど)を把握できるようにしている。 - 開発者が、
runtime stable class …は問題ではなく、コンパイラーが遅延安定性を正しく出力したものだと理解している。
参考資料
- Android Developers — 安定性の診断:https://developer.android.com/develop/ui/compose/performance/stability/diagnose
- Android Developers — 安定性の概要:https://developer.android.com/develop/ui/compose/performance/stability
- Compose Compiler のリリースノート:https://developer.android.com/jetpack/androidx/releases/compose-compiler
- Ben Trengrove — Jetpack Compose の安定性を解説:https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8
- Chris Banes — Composable Metrics:https://chrisbanes.me/posts/composable-metrics/
- リリースビルドでパフォーマンスをテストする理由:https://medium.com/androiddevelopers/why-should-you-always-test-compose-performance-in-release-4168dd0f2c71
- skydoves — 安定性を極めてアプリのパフォーマンスを最適化:https://medium.com/proandroiddev/optimize-app-performance-by-mastering-stability-in-jetpack-compose-69f40a8c785d
- skydoves — compose-stability-analyzer:https://github.com/skydoves/compose-stability-analyzer
references/reading-classes-txt.md— classes.txt の文法全体と、詳細な例(stable/unstable/runtime/generic)。references/reading-composables-txt.md— composables.txt の文法全体。restartable、skippable、readonly、scheme、パラメーターごとのstable/unstable/@static/@dynamicを含みます。