Listの変更によるRecomposition

Listの変更によるRecompositionがどのように実行されるか、それを効率よく行う方法を見ていきます。

Photo by Cristina Gottardi on Unsplash

Jetpack Composeにおいて状態が変化したときにUIに反映するRecompositionという仕組みがあります。Recompositionでは変更された状態のみComposable関数を実行しますが、その判断にはソースコードの呼び出し箇所によって識別されます。

Listのようにループを使って実行する場合には、その順番とインスタンスからRecomposition対象なのかを判断します。そのためListの変更が起きた場合に不要なRecompositionがことがあります。

Listの変更によるRecompositionがどのように実行されるか、それを効率よく行う方法を見ていきます。

実装例

例として以下のようなコードで試してきます。

Todoというdata classのListを一覧表示する例です。

使用されてるListは以下のような感じでStateとして扱えるようにして、TodoListというComposable関数に渡している状態です。

Listの末尾にデータを追加

こんな感じでListの末尾にデータが追加された場合についてです。

末尾にデータ追加

単純に追加されたデータのみがComposable関数が実行され、元々あったデータのRecompositionは実行されません。

これは期待される挙動だと思います。

Listの先頭にデータを追加

追加するindexを指定して、Listの先頭に追加された場合についてです。

先頭にデータ追加

Listの先頭にデータが追加された場合は以前の順番とインスタンスが異なるため、変更していないところも含めてすべてComposable関数が実行されます。

Listの途中にデータを追加

最後に途中にデータを追加したときを見てみます。

途中にデータ追加

途中に追加した場合は、追加されたデータ以降のComposable関数が実行されることになります。

Recompositionが実行されるものとしては前回の順番とインスタンスが異なってる場合になります。なので、Listの順番を入れ替えた場合などもRecompositionが実行されることになります。

問題点

順番によるRecompositionの判定では効率が悪く、また場合によっては予期せぬ挙動になることもあります。例えば画像を取得中にRecompositionが行われるとキャンセルされて再開されるという動作になったりします。

ListのRecompositionを制御する

順番が変更されてもデータが同じであればRecompositionをしてほしくない場合に対応する方法があります。

次のようにループ内にて key を使って、一意になる情報を渡すことでRecompositionするかの判断を助けることができます。

こうすることで key に渡された値を元にRecompositionをするかどうかも判定されることになります。

これを使うと、先頭にデータが追加された場合でも、先程とはことなり、追加されたデータのComposable関数が実行されるだけで、元々あったデータではRecompositionは実行されません

先頭にデータ初歌

key には複数の値が渡せるので複合Keyのようなものにも対応できるようになっています。

また、 key 渡す値はList内で一意であればよいので、別のループ内で同じ値を使用しても問題ありません。

LazyColumn などでkeyを指定したい場合は、以下のような感じで指定できます。

Jetpack ComposeでListを扱うときはRecompositionがどのように行われるかを理解して、必要あれば keyを使って制御する必要があります。

参考

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO