7
7

More than 1 year has passed since last update.

LazyColumn/RowでLazyListStateにアクセスするタイミングを間違えるとパフォーマンスが悪くなる

Last updated at Posted at 2021-11-18

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 が有効な時には挙動が遅くなっているといった話もあります。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7