- バックエンド / リーダー候補
- PdM
- Webエンジニア(シニア)
- 他19件の職種
- 開発
- ビジネス
Stability configuration fileでComposeのStabilityを改善する
Photo by Ricardo Gomez Angel on Unsplash
Mobile Tech leadの久保出です。今回は課題解決の間に見つけたJetpack Composeの新機能を紹介します。
課題
Wantedly VisitのAndroidアプリ開発では、モバイルネイティブアプリの共通ロジック実装はKotlin Multiplatformを使い、UI実装はJetpack Composeを使っています。
最近の課題として、Jetpack Composeで実装したUIのパフォーマンスが悪く、Jank frameが発生しやすい問題があります。具体的には、スクロール中にカクつくとか状態が変わったときにワンテンポ遅れるときがあります。
一つの要因として、Stabilityの問題があります。Jetpack Composeでは状態の差分によるUIの差分更新が行われます。それには差分比較の対象クラスがStableであることが求められます。詳しくはStabilityのドキュメントを参照してください。
Kotlin Multiplatformで定義されたクラスは、ビルド済みのバイナリであるため、後からアノテーションを付与することはできません。Kotlin MultiplatformにComposeのアノテーションだけ依存させたり、wrapperクラスを作るなどで改善することはできますが、アノテーションの方法では低レイヤーが上位レイヤーの実装を知る必要が出てきたり、wrapperの方法はボイラープレートが増えるなど、別の課題が出てきます。
Stability configuration file
試行錯誤の中でAndroid公式ドキュメントのFix stability issuesを見ていたところ、Stability configuration fileが最近増えていました。
Stability configuration fileは、Compose compilerに対して設定ファイルを指定することで、自分の手の及ばないクラスをStable扱いにできる機能です。Compose compiler 1.5.5から追加されています。これにより、LocalDateTime
のような標準APIのクラス、外部SDKやライブラリで定義されたクラスなどをStable扱いにできます。
課題としてあった、Kotlin MultiplatformのクラスのStabilityの問題もこれで解決できます。
Gradleの設定
基本的な使い方は公式ドキュメントを見てください。我々は以下のような拡張関数を作りました。これにより、Compose設定を一括で行う自作プラグインに適用しています。
// If you want to use this, run `./gradlew :module:assembleRelease -PcomposeCompilerReports=true`
// https://developer.android.com/jetpack/compose/performance/stability/diagnose#compose-compiler
fun Project.configureComposeCompilerReports() {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
if (project.findProperty("composeCompilerReports") == "true") {
freeCompilerArgs += listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:" +
"reportsDestination=${layout.buildDirectory.dir("compose_compiler").get()}",
)
}
if (project.findProperty("composeCompilerMetrics") == "true") {
freeCompilerArgs += listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:" +
"metricsDestination=${layout.buildDirectory.dir("compose_compiler").get()}",
)
}
}
}
}
// If you want to override the stability configuration, create `compose_compiler_stability_config.conf` to module root.
// https://developer.android.com/jetpack/compose/performance/stability/fix#configuration-file
fun Project.configureComposeStabilityConfigurationFile() {
val stabilityConfigurationFile = layout.projectDirectory.file("compose_compiler_stability_config.conf").asFile
if (!stabilityConfigurationFile.exists()) {
return
}
logger.info("Found `${stabilityConfigurationFile.name}` in $path. Use it as a stability configuration file.")
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs += listOf(
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
stabilityConfigurationFile.absolutePath,
)
}
}
}
Compose compiler reportsの設定と、モジュールのルートにcompose_compiler_stability_config.conf
を配置することで、そのモジュールにだけ設定を有効にできるようにしています。
我々はcompose_compiler_stability_config.confを
このような内容にしています。
// Consider kotlin collections stable
kotlin.collections.List
kotlin.collections.Set
kotlin.collections.Map
// Consider GraphQL schema stable
com.wantedly.visit.app.shared.api.graphql.*
com.wantedly.visit.app.shared.api.graphql.fragment.*
// Reactor.State
com.wantedly.visit.app.shared.feature.search.SearchReactor.State
com.wantedly.visit.app.shared.feature.search.SearchReactor.State.*
KotlinのListはインターフェースであるためUnstableであることはよく知られていますが、プロジェクト内でのユースケースとしてUnstableになる使い方をしていない場合、Listを設定に追加することでStableにできます。
Apollo Kotlinでコード生成されたデータクラスやUIの状態クラスもImmutableであるとわかっているため、それらも追加しています。
結果
Compose compiler reportsの結果を前後で比較すると、Composableの半分近くがUnstableによるスキップ不可でしたが、設定によりほとんどはスキップ可になりました。
注意点
ドキュメントで警告されている通り、設定を誤るとRecompositionが破壊されレンダリングがおかしくなります。これは@Immutable
などにも同じことは言えますが、Stability configuration fileのほうが間違えに気づきづらいので、より慎重に扱うべきです。
まとめ
Stability configuration fileについて紹介しました。この設定により、Composeのパフォーマンスを向上できる場合があります。ただし、注意点があるため使用すべきかどうかも含めてチームで決める必要があります。
実際に使ってみて、パフォーマンスに変化があったかというと、あまり変化はありませんでした。RecompositionではなくComposition自体のコストが高いためです。これは計測段階でもなんとなくわかっていましたが、もう少し効果が出てほしかったところです。Composable自体の組み方を見直したりComposableを細分化など、別のアプローチを取る必要があるので、やれることを試していこうと思います。