Compose の安定性

Compose は、型が安定版か不安定版かを考慮します。型が不変の場合、または再コンポーズ間で値が変更されたかどうかを Compose が認識できる場合、その型は安定しています。再コンポーズ間で値が変更されたかどうかを Compose が認識できない場合、型は不安定になります。

Compose は、コンポーザブルのパラメータの安定性を使用して、再コンポーズ中にコンポーザブルをスキップできるかどうかを決定します。

  • 固定パラメータ: コンポーザブルに、変更されていない固定パラメータがある場合、Compose はそれをスキップします。
  • 不安定なパラメータ: コンポーザブルに不安定なパラメータがある場合、Compose はコンポーネントの親を再コンポーズするときに常に再コンポーズします。

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 は、そのようなクラスを不安定であると見なします。Compose は、不安定なクラスの再コンポーズをスキップしません。そのため、Contact がこの方法で定義されている場合、前の例の ContactRow は、selected が変更されるたびに再コンポーズします。

Compose での実装

再コンポーズの際にスキップする関数を Compose が正確に決定する方法を検討することは必須ではありませんが、重要ではありません。

Compose コンパイラをコードで実行すると、各関数と型が複数のタグのいずれかでマークされます。これらのタグは、再コンポーズの際に Compose が関数または型を処理する方法を反映しています。

関数

Compose では、関数を skippable または restartable としてマークできます。関数を一方または両方としてマークすることも、どちらともマークしない場合もあります。

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

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

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

デバッグの安定性

パラメータが変更されていないコンポーザブルを再コンポーズする場合は、まず、明確に変更可能なパラメータの定義を確認します。var プロパティを含む型を渡すか、既知の不安定な型を使用する val プロパティを渡すと、Compose は常にコンポーネントを再コンポーズします。

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

安定性に関する問題を解決する

Compose の実装に安定性をもたらす方法については、安定性の問題を解決するのガイドをご覧ください。

まとめ

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

  • パラメータ: Compose は、コンポーザブルの各パラメータの安定性を判断して、再コンポーズ時にスキップするコンポーザブルを決定します。
  • 即時修正: コンポーザブルがスキップされず、パフォーマンスの問題を引き起こしている場合は、まず var パラメータなど、不安定性の明らかな原因を確認する必要があります。
  • コンパイラ レポート: コンパイラ レポートを使用して、クラスに関して推測されている安定性を確認できます。
  • コレクション: Compose は常に、List, SetMap などのコレクション クラスが不安定であると見なします。これは、それらが不変であることが保証できないためです。代わりに Kotlinx 不変コレクションを使用するか、クラスに @Immutable または @Stable のアノテーションを付けることができます。
  • その他のモジュール: Compose コンパイラが実行されないモジュールの場合、Compose は常に不安定であると見なします。必要に応じて、クラスを UI モデルクラスでラップします。

参考資料