ある成果物の開発を進めるに時に、「先に問題へ対応する」と「後で問題へ対応する」では、後者の方が難易度は高いです。
開発は進むにつれて周辺との因果関係が複雑化ます。全体に影響を与えない形で問題へ対応することが、難しくなるからです。
不要な再Composeを回避する対応も同じだと思います。
ですので、発生源が明確で不要な再Composeは、コーディングの段階で潰しておくべきです。
ただし、不要な再Composeの発生源を知らなければ出来ません。
ここで紹介するList型入力は、不要な再Composeの発生源の一つです。
※環境:Android Studio Koala | 2024.1.1
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.foundation:foundation 1.6.8
kotlinx.collections.immutable 0.3.7
List型入力による再Composeの発生
LazyColumnで一覧(リスト)表示するデータをList型で入力している場合、再Composeがスケジュールされます。
@Stable data class NumData(val id: Int, var num: Int) val datas = List(12){ NumData(id = it, num = (Math.random() * 100).toInt()) } @Composable fun SampleListPanel() { Box(modifier = Modifier.fillMaxSize()) { val _count = remember { mutableStateOf(0) } SampleList(datas = datas) // List型で入力⇒再Composeをスケジュール Button(...) { Text(text = "Renew") } Button( // この階層で再Composeを実施 modifier = Modifier.padding(5.dp).align(Alignment.TopStart), onClick = { _count.value ++ } ) { Text(text = "Compose ${_count.value}") } } } @Composable fun SampleList(datas: List<NumData>) { // List型で入力⇒不安定な型 LazyColumn { items(items = datas) { ItemBox { Text(text = "Item %02d".format(it.num)) } } } }
これは、ComposeコンパイラーがList型を不安定な型と暗黙的に認識するためです。
不安定な型を入力とするComposable関数は必ず再Composeの対象になります。
「Listの要素が変わらない」、つまり「アイテムの表示が変わらない」のであれば、この再Composeは不要です。
不要な再Composeを回避するには、List型を不変コレクションへ変換します。これには2つの方法があります。
- (1)@Immutableの指定
- (2)ImmutableListの作成
(1)再Composeを回避(@Immutableの指定)
ラッパークラスで囲み、アノテーション@Immutableを付与すると、そのクラスは不変クラスになります。
@Stable data class NumData(val id: Int, var num: Int) @Immutable // ラッパーで囲み、不変クラスへ変換 data class ImmDatas( val values: List<NumData> = List(12){ NumData(id = it, num = (Math.random() * 100).toInt()) } ) val datas = ImmDatas() @Preview_mdpi320x480 @Composable fun SampleListPanel() { Box(modifier = Modifier.fillMaxSize()) { val _count = remember { mutableStateOf(0) } SampleList(datas = datas) // 不変⇒再Composeをスケジュールしない Button(...) { Text(text = "Renew") } Button( modifier = Modifier.padding(5.dp).align(Alignment.TopStart), onClick = { _count.value ++ } ) { Text(text = "Compose ${_count.value}") } } } @Composable private fun SampleList(datas: ImmDatas) { // 不変⇒安定した型 LazyColumn { items(items = datas.values) { ItemBox { Text(text = "Item %02d".format(it.num)) } } } }
(2)再Composeを回避(ImmutableListの作成)
Listの代わりにImmutableListを使います。ImmutableListはkotlinx APIが提供する不変コレクションです。
ComposeコンパイラはImmutableListをサポートしていて、暗黙的に安定した型と認識します。
@Stable data class NumData(val id: Int, var num: Int) fun createDatas() = List(12){ NumData(id = it, num = (Math.random() * 100).toInt()) }.toImmutableList() // 不変コレクション(ImmutableList)へ変換 val datas = createDatas() @Preview_mdpi320x480 @Composable fun SampleListPanel() { Box(modifier = Modifier.fillMaxSize()) { val _count = remember { mutableStateOf(0) } SampleList(datas = datas) // 不変⇒再Composeをスケジュールしない Button(...) { Text(text = "Renew") } Button( modifier = Modifier.padding(5.dp).align(Alignment.TopStart), onClick = { _count.value ++ } ) { Text(text = "Compose ${_count.value}") } } } @Composable private fun SampleList(datas: ImmutableList<NumData>) { // 不変⇒安定した型 LazyColumn { items(items = datas) { ItemBox { Text(text = "Item %02d".format(it.num)) } } } }
kotlinxのImmutableListを使う場合は、ライブラリ「kotlinx.collections.*」の追加が必要です。「Maven Central」から提供されています。
詳細は「Immutable Collections Library for Kotlin」を参照してださい。
関連記事: