LazyColumn
/ LazyRow
の引数である content の LazyListScope
内で LazyListState
へのアクセスすると必要以上に recompose されるようになりパフォーマンスが悪くなる場合があります。
良くない実装と改善するとしたらのコードを書いていきます。
コード例
良くない例
@Composable
fun VerticalList(items: List<String>, onReachedBottom: () -> Unit) {
val listState = rememberLazyListState()
val currentOnReachedBottom by rememberUpdatedState(onReachedBottom)
LazyColumn(state = listState) {
items(items) {
Text(text = it)
}
if (listState.firstVisibleItemIndex + listState.layoutInfo
.visibleItemsInfo.size == listState.layoutInfo.totalItemsCount) {
currentOnReachedBottom()
}
}
}
LazyColumn
の中で LazyListState
にアクセスして何かしらの処理をしているので、LazyColumn
が何度も recompose され続けることになりパフォーマンスが悪くなります。
改善例
このコードを改善するには以下の例のように LazyListState
の分岐処理を LazyColumn
から出してあげる必要があります。
@Composable
fun VerticalList(items: List<String>, onReachedBottom: () -> Unit) {
val listState = rememberLazyListState()
val currentOnReachedBottom by rememberUpdatedState(onReachedBottom)
val isReachedBottom by remember {
derivedStateOf {
listState.firstVisibleItemIndex + listState.layoutInfo
.visibleItemsInfo.size == listState.layoutInfo.totalItemsCount
}
}
LaunchedEffect(isReachedBottom) {
snapshotFlow { isReachedBottom }
.collect { isReached ->
if (isReached) {
currentOnReachedBottom()
}
}
}
LazyColumn(state = listState) {
items(items) {
Text(text = it)
}
}
}
LazyListState
を使用した分岐処理を derivedStateOf
で変数として取り出すようにしています。
snapshotFlow
にした箇所は一番下までスクロールした時に発火する処理になっています。
これで LazyColumn
内に処理が入らないため必要以上に recompose されなくなります。
なぜパフォーマンスが悪くなるのか
悪い例のコードの処理を詳しく追っていくと、
-
LazyColumn
の content が処理されて表示されるアイテムの数が確定してレイアウトされる- アイテムの数が確定する時に
LazyListState
が更新される
- アイテムの数が確定する時に
-
LazyListState
が更新されることでLazyColumn
の content に変更が入ることになるので再度処理が実行される
@Composable
fun VerticalList(items: List<String>, onReachedBottom: () -> Unit) {
// 2.アイテム数が確定することでLazyListStateが更新される
// 4.contentが処理されることでLazyListStateが更新されることになる
val listState = rememberLazyListState()
val currentOnReachedBottom by rememberUpdatedState(onReachedBottom)
LazyColumn(state = listState) {
// 1.item/itemsの処理がされてアイテムの数が確定する
items(items) {
Text(text = it)
}
// 3.LazyListStateが更新されたことでここやcontentの処理も更新されることになる
// 5.再びここが更新されることになって更新が繰り返される
if (listState.firstVisibleItemIndex + listState.layoutInfo
.visibleItemsInfo.size == listState.layoutInfo.totalItemsCount) {
currentOnReachedBottom()
}
}
}
という感じになるので、LazyColumn
の content の中で LazyListState
を参照している場合に、常に更新されることになってパフォーマンスが悪くなります。
LazyColumn
/ LazyRow
のパフォーマンス改善には key を設定しておくとか、そもそも Jetpack Compose は debuggable
が有効な時には挙動が遅くなっているといった話もあります。