🚀

Composeのstate監視がどこまでされるのか試してみた

2021/05/06に公開

背景(?)

Jetpack Compose(以下Compose)ではState<T>というものがあります。
これはComposeにおいて状態を管理するためのもので、ComposeはこのState<T>の変更を監視し値が変更されるとそのStateを利用しているComposableを自動で再読み込みして値の変更を見た目に反映させます。
詳細は以下リンク先にあります。
https://developer.android.com/jetpack/compose/state?hl=ja

そしてこのStateは以下のように使うこともできます。

class TodoViewModel : ViewModel() {

   // private state
   private var currentEditPosition by mutableStateOf(-1)

   // state
   var todoItems by mutableStateOf(listOf<TodoItem>())
       private set

   // state
   val currentEditItem: TodoItem?
       get() = todoItems.getOrNull(currentEditPosition)

   // ..

コードは以下Codelabから引用

https://developer.android.com/codelabs/jetpack-compose-state#8

このコードではcurrentEditPositionInttodoItemsList<TodoItem>として利用できますが、by mutableStateOfを使って定義しているため、Composeの監視対象とされています。

そして、currentEditItemはこの2つの状態を利用しています。
こういった書き方をすることで、ComposeはcurrentEditPositiontodoItemsの変更を監視しつつ、どちらかに変更があったタイミングでcurrentEditItemのゲッターを再度呼び出し見た目に反映させます。
注意点としては、値が変更されたことをトリガーにしたい値(currentEditPositionやtodoItems)をby mutableStateOf= mutableStateOfでStateオブジェクトとして定義する必要があります。

本題

ここで、この状態をゲッターで利用している値の監視はどこまで継続されるのか気になったため以下のコードの動作を確かめてみました。

コード

class MainViewModel : ViewModel() {
  var count1 by mutableStateOf(0)
    private set
  var count2 by mutableStateOf(0)
    private set

  val countResult1: Int
    get() = count1 + count2
  val countResult2: Int
    get() = countResult1 + 1
  val countResult3: Int
    get() = countResult2 + 1
  val countResult4: Int
    get() = countResult3 + 1
  val countResult5: Int
    get() = countResult4 + 1
  val countResult6: Int
    get() = countResult5 + 1
  val countResult7: Int
    get() = countResult6 + 1
  val countResult8: Int
    get() = countResult7 + 1
  val countResult9: Int
    get() = countResult8 + 1
  val countResult10: Int
    get() = countResult9 + 1
  val countResult11: Int
    get() = countResult10 + 1
  val countResult12: Int
    get() = countResult11 + 1
  val countResult13: Int
    get() = countResult12 + 1
  val countResult14: Int
    get() = countResult13 + 1
  val countResult15: Int
    get() = countResult14 + 1
  val countResult16: Int
    get() = countResult15 + 1
  val countResult17: Int
    get() = countResult16 + 1
  val countResult18: Int
    get() = countResult17 + 1
  val countResult19: Int
    get() = countResult18 + 1
  val countResult20: Int
    get() = countResult19 + 1
  val countResult21: Int
    get() = countResult20 + 1

  fun onClickCount() {
    count1++
  }

  fun onClickCount2() {
    count2++
  }
@Composable
fun Main() {
  val viewModel: MainViewModel = viewModel()

  Column {
    Text(text = "Count1:${viewModel.count1}")
    Text(text = "Count2:${viewModel.count2}")

    Spacer(modifier = Modifier.height(10.dp))

    Text(text = "Result1:${viewModel.countResult1}")
    Text(text = "Result2:${viewModel.countResult2}")
    Text(text = "Result3:${viewModel.countResult3}")
    Text(text = "Result4:${viewModel.countResult4}")
    Text(text = "Result5:${viewModel.countResult5}")
    Text(text = "Result6:${viewModel.countResult6}")
    Text(text = "Result7:${viewModel.countResult7}")
    Text(text = "Result8:${viewModel.countResult8}")
    Text(text = "Result9:${viewModel.countResult9}")
    Text(text = "Result10:${viewModel.countResult10}")
    Text(text = "Result11:${viewModel.countResult11}")
    Text(text = "Result12:${viewModel.countResult12}")
    Text(text = "Result13:${viewModel.countResult13}")
    Text(text = "Result14:${viewModel.countResult14}")
    Text(text = "Result15:${viewModel.countResult15}")
    Text(text = "Result16:${viewModel.countResult16}")
    Text(text = "Result17:${viewModel.countResult17}")
    Text(text = "Result18:${viewModel.countResult18}")
    Text(text = "Result19:${viewModel.countResult19}")
    Text(text = "Result20:${viewModel.countResult20}")
    Text(text = "Result21:${viewModel.countResult21}")

    Button(onClick = viewModel::onClickCount) {
      Text(text = "count")
    }
    Button(onClick = viewModel::onClickCount2) {
      Text(text = "count2")
    }
  }
}

結果

このコードを実際に動かしてみたときの動きは以下のようになります。

2つのボタンいずれかを押すとResult1〜Result21の部分まで値が更新されました。

コードを生成するコードを作成して、変数をいくつまで変更検知できるか試してみました。

f = open('foo.txt', 'w', encoding='UTF-8')
for i in range(8000):
    f.write('val countResult%s: Int\n' % (i + 1))
    f.write('get() = countResult%s + 1\n' % (i))

f.close()

結果として確認できたのは8000個の変数まで変更検知されていました。

それ以上の変数(8500個以上)も試してみましたが、そちらはビルド時にClass too largeとなってしまい検証はできませんでした。

これらのことから、ビルドできる範囲内であれば変数がいくつあっても変更検知できそうですので、特に数は気にせず利用できそうです。

Discussion