Mutable系統をStateに入れていて、数日ドツボにはまったのでメモしておきます。
問題
Jetpack Composeの勉強のために、以下のようなアプリを作成しました。
ソースコードは以下の通りです。
@Composable
fun List(){
//表示するStateを作成
val list by remember { mutableStateOf((0..3).toMutableList()) }
LazyColumn(Modifier.fillMaxSize()){
//カラムを表示する部分
item{
for(i in list){
LazyCard(text = i.toString())
}
}
//ボタンを表示する部分
item {
AddButton(onClick = {
list.add(list.size)
})
}
}
}
これでボタンを押せば、カラムが増えていくはず・・・ですがなぜか増えません。
解決策?
原因ですが、おそらく
//ボタンを表示する部分
item {
AddButton(onClick = {
list.add(list.size)
})
}
ここでadd
関数を使っていますが、これでは差分を検知してくれないからではないかと思います。
そこでこの部分をとりあえず以下のように書き直してみました。
//表示するStateを作成
val list by remember { mutableStateOf((0..3).toMutableList()) }
・・・
//ボタンを表示する部分
item {
AddButton(onClick = {
val tempList = list
tempList.add(list.size)
list = tempList
})
}
これで、listに再代入しているので差分を検知してくれるはず・・・がまたしてもピクリともしない。
原因
なぜ検知してくれないのかと思い、tempList
に要素を追加したタイミングでそれぞれの値を見てみました。
//ボタンを表示する部分
item {
AddButton(onClick = {
val tempList = list
tempList.add(list.size)
Log.d("debug", "list: $list, tempList: $tempList")
list = tempList
})
}
すると驚くことに、以下の結果が出力されました。
D/debug: list: [0, 1, 2, 3, 4], tempList: [0, 1, 2, 3, 4]
これはつまり
tempList.add(list.size)
が実行されたタイミングでlist
の方にも反映されているということです。
ということは
val tempList = list
ここでの代入は値渡しではなく、参照渡しになっている?ということでしょう。
確かにそう考えると、add
関数では差分を検知しないし、最後の代入でも差分がない。
UIの再描写が走らないはずです。
解決策
GoogleのCodelabを見て回っていたら、このような感じで記述していました。
var list by remember { mutableStateOf((0..3).toList()) }
・・・
//ボタンを表示する部分
item {
AddButton(onClick = {
list = list + listOf(list.size)
})
}
なるほど・・・完全に新しく作り直していますね。
確認したところ、これだと正しく動作しました。
となってくると、StateでMutable系統を使うのはやめた方がよさそうですね。
add
関数が使えない時点で通常の型の代わりに使うメリットがないし、むしろバグの温床になりそうです。
補足
参照渡しになるのはKotlin(正確にはJava)の仕様のようですね。
プリニティブ型では値渡し、クラス型では参照渡しをするようで、Mutable系統はもちろんクラス型なので参照渡しになるということのようです。
参考