Compose の安定性

Compose では、型は安定または不安定のいずれかであると見なされます。タイプが安定しているということは、不変であるか、再コンポーズの間に値が変化したかどうかを Compose が把握できるということです。タイプが不安定であるということは、再コンポーズの間に値が変化したかどうかを Compose が把握できないということです。

Compose は、コンポーズ可能な関数のパラメータの安定性を使用して、入力の比較方法を決定し、再コンポーズ中にコンポーズ可能な関数をスキップできるかどうかを判断します(Kotlin 2.0.20 以降では、強力なスキップモードがデフォルトで有効になっています)。

  • 安定したパラメータ: Compose は、構造の等価性(Object.equals())を使用して安定したパラメータを比較します。コンポーザブルの安定したパラメータが以前の値と等しい場合、Compose はそのコンポーザブルをスキップします。
  • 不安定なパラメータ: 強いスキップモードが有効になっている場合(Kotlin 2.0.20 以降のデフォルト)、Compose はインスタンスの等価性(===)を使用して不安定なパラメータを比較し、同じオブジェクト インスタンスが渡された場合はコンポーザブルをスキップします。Kotlin 2.0.20 より前のバージョン(または強いスキップモードが無効になっている場合)では、Compose は親が再コンポーズされると、不安定なパラメータを持つコンポーザブルを常に再コンポーズします。

アプリが不安定なパラメータの新しいインスタンスを頻繁に割り当てる場合や、モデルに過剰なアノテーションを付けることで大規模なコレクションに対してコストの高い .equals() チェックを強制する場合、不要な再コンポーズや比較オーバーヘッドが発生する可能性があります。

このドキュメントでは、Compose が安定性を判断する方法と、安定性を最適化してパフォーマンスと全体的なユーザー エクスペリエンスを向上させる方法について詳しく説明します。

不変オブジェクト

次のスニペットは、安定性と再コンポジションの背後にある一般的な原則を示しています。

Contact クラスは不変のデータクラスです。これは、すべてのパラメータが val キーワードで定義されたプリミティブであるためです。Contact のインスタンスを作成すると、オブジェクトのプロパティの値を変更することはできません。そうしようとすると、新しいオブジェクトが作成されます。

data class Contact(val name: String, val number: String)

ContactRow コンポーザブルには Contact 型のパラメータがあります。

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}

ユーザーが切り替えボタンをクリックして selected 状態が変化すると、次のようになります。

  1. Compose は、ContactRow 内のコードを再コンポーズする必要があるかどうかを評価します。
  2. ContactDetails の唯一の引数が Contact 型であることがわかります。
  3. Contact は不変データクラスであるため、Compose は ContactDetails の引数が変更されていないことを確認できます。
  4. そのため、Compose は ContactDetails をスキップし、再コンポーズしません。
  5. 一方、ToggleButton の引数は変更されているため、Compose はそのコンポーネントを再コンポーズします。

可変オブジェクト

上記の例では不変オブジェクトを使用していますが、可変オブジェクトを作成することもできます。次のスニペットを考えてみましょう。

data class Contact(var name: String, var number: String)

Contact の各パラメータが var になったため、クラスは不変ではなくなりました。プロパティが変更されても、Compose は認識しません。これは、Compose が Compose の State オブジェクトへの変更のみを追跡するためです。

Compose は、このようなクラスを不安定と見なします。強力なスキップモード(Kotlin 2.0.20 以降)では、Compose はインスタンスの等価性(===)を使用して Contact を比較します。contact.name をインプレースで変更しても再コンポーズはトリガーされませんが、同じ値を持つ新しく割り当てられた Contact インスタンスを渡すと、ContactDetails は強制的に再コンポーズされます(強力なスキップモードを使用しない場合、ContactDetails は selected が変更されるたびに再コンポーズされます)。

Compose での実装

Compose が再コンポーズ中にスキップする関数を正確に判断する方法を検討することは、重要ではありませんが、役立つ場合があります。

Compose コンパイラは、コードを実行するときに、各関数と型にいくつかのタグのいずれかを付けます。これらのタグは、再コンポーズ中に Compose が関数または型を処理する方法を反映しています。

関数

Compose は、関数を skippable または restartable としてマークできます。関数は、次のいずれか、両方、またはどちらでもないものとしてマークされることがあります。

  • スキップ可能: コンパイラがコンポーザブルをスキップ可能としてマークすると、Compose は、すべての引数が以前の値と等しい場合、再コンポーズ中にそのコンポーザブルをスキップできます。(Kotlin 2.0.20+ の強いスキップモードでは、再起動可能なすべてのコンポーザブルが自動的にスキップ可能になります)。
  • 再開可能: 再開可能なコンポーザブルは、再コンポーズを開始できる「スコープ」として機能します。つまり、この関数は、状態の変更後に Compose が再コンポジションのためにコードの再実行を開始できるエントリ ポイントになります。

型

Compose は、型を不変または安定としてマークします。各タイプは次のいずれかです。

  • 不変: Compose は、プロパティの値が変更されることがなく、すべてのメソッドが参照透過的である場合、型を不変としてマークします。
    • すべてのプリミティブ型は不変としてマークされます。これには、String、Int、Float が含まれます。
  • Stable: 構築後にプロパティを変更できる型を示します。実行時にこれらのプロパティが変更されると、Compose はそれらの変更を認識します。

デバッグの安定性

パラメータが変更されていないコンポーザブルがアプリで再コンポーズされている場合は、まず、再コンポーズのたびに不安定な型の新しいインスタンスが割り当てられているかどうかを確認します(または、強スキップモードが無効になっている場合は、コンポーザブルに var プロパティまたは不安定な型の val プロパティを持つパラメータがあるかどうかを確認します)。

Compose の安定性に関する複雑な問題を診断する方法については、安定性のデバッグガイドをご覧ください。

安定性の問題を修正する

Compose の実装を安定させる方法については、安定性の問題を修正するガイドをご覧ください。

概要

全体として、次の点に注意してください。

  • パラメータ: Compose は、コンポーザブルの各パラメータの安定性を判断して、再コンポーズ中に構造の等価性(.equals())またはインスタンスの等価性(===)を使用して比較するかどうかを決定します。
  • 即時の修正: コンポーズ可能な関数がスキップされておらず、パフォーマンスの問題が発生していることに気付いた場合は、不安定なパラメータの新しいインスタンスがパスごとに再作成されているかどうか、または State の代わりに var プロパティが使用されているかどうかを確認します。
  • コンパイラ レポート: コンパイラ レポートを使用すると、クラスについて推測されている安定性を確認できます。
  • コレクション: Compose は、標準のコレクション インターフェース(List、Set、Map)を不安定と見なします。これは、基盤となる実装が変更可能である可能性があるためです。強力なスキップモード(Kotlin 2.0.20 以降ではデフォルトで有効)では、コレクション参照が変更されない場合、不安定なコレクションはインスタンスの等価性(===)を使用して再コンポーズをスキップします。標準のコレクションを Kotlinx の不変コレクションに置き換えたり、コレクションを保持するクラスに @Immutable または @Stable のアノテーションを付けたりする場合は、注意が必要です。再コンポーズのパスごとにすべての要素を .equals() と比較すると、再コンポーズ自体よりもコストが高くなる可能性があります。
  • 他のモジュール: Compose は、Compose コンパイラが実行されないモジュールのクラスを不安定とみなします(強力なスキップモードで === を使用して比較)。データソースが、同一の値を持つ Compose 以外のモジュールから小さなフラットモデルを頻繁に再インスタンス化する場合は、安定性構成ファイルを構成するか、.equals() が安価な @Stable または @Immutable を使用できます。

関連情報