ある成果物の開発を進めるに時に、「先に問題へ対応する」と「後で問題へ対応する」では、後者の方が難易度は高いです。
開発は進むにつれて周辺との因果関係が複雑化ます。全体に影響を与えない形で問題へ対応することが、難しくなるからです。
不要な再Composeを回避する対応も同じだと思います。
ですので、発生源が明確で不要な再Composeは、コーディングの段階で潰しておくべきです。
ただし、不要な再Composeの発生源を知らなければ出来ません。
ここで紹介するListStateは、不要な再Composeの発生源の一つです。
※環境:Android Studio Koala | 2024.1.1
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.foundation:foundation 1.6.8
ListStateによる再Composeの発生
ListStateは一覧(リスト)表示の状態を保持しています。そして、スクロールを行うと、状態は時々刻々と変化します。
ですので、状態の読み出しを行った場合、スクロールで発生する状態の変化に合わせて再Composeがスケジュールされます。
以下は状態(LazyListState#firstVisibleItemIndex)に合わせて、ボタンを表示/非表示する例です。
@Composable fun ListStateSample() { Box(modifier = Modifier.fillMaxSize()) { val _listState = rememberLazyListState() val _coroutineScope = rememberCoroutineScope() val _visible = _listState.firstVisibleItemIndex > 3 // true:表示、false:非表示 LazyColumn(state = _listState) { items(24) { ItemBox { Text(text = "item %02d".format(it)) } } } AnimatedVisibility( // ボタンの表示/非表示を切り替え modifier = Modifier.align(Alignment.BottomEnd).padding(10.dp), visible = _visible ) { Button( onClick = { _coroutineScope.launch { _listState.scrollToItem(index = 0) // 一覧表示の先頭へジャンプ } } ) { Text(text = "Top") } } } }
この例は、一覧表示の先頭のアイテムが変わる毎に再Composeが発生します。
しかし、再Composeが必要なのはボタンの表示と非表示を切り替える時であって、その他は不要な再Composeです。
再Composeを回避(derivedStateOfで分離)
derivedStateOf( )関数を利用すると、一覧表示の状態を持つListStateオブジェクトを、指定した計算式の結果を状態に持つオブジェクトに変換できます。
@StateFactoryMarker fun <T> derivedStateOf( calculation: () -> T, // 計算式 ): State<T> = DerivedSnapshotState(calculation, null)
以下は、indexの変化の中から「index > 3」(指定した計算式の結果)になる変化を分離しています。これにより、必要な再Composeのみが行われます。
@Composable fun ListStateSample() { Box(modifier = Modifier.fillMaxSize()) { val _listState = rememberLazyListState() val _coroutineScope = rememberCoroutineScope() val _visible = remember { derivedStateOf { _listState.firstVisibleItemIndex > 3 } } LazyColumn(state = _listState) { items(24) { ItemBox { Text(text = "item %02d".format(it)) } } } AnimatedVisibility( modifier = Modifier.align(Alignment.BottomEnd).padding(10.dp), visible = _visible.value ) { Button( onClick = { _coroutineScope.launch { _listState.scrollToItem(index = 0) } } ) { Text(text = "Top") } } } }
関連記事: