見出し画像

LazyColumnの各アイテム内にLaunchedEffectを置いて実験してみた!

社内で下記のコードラボをやった時に、LaunchedEffectを書く場所によって違いはあるのか?が話題になりました。

私は特にLazyColumnの各アイテム内にLaunchedEffectを置いたときにどうなるんだろう…?といったところが気になったので、ちょっと実験してみました🤸🏼‍♀️

Case1: Columnの子要素にLaunchedEffectを仕込んでみる

まずはColumnの子要素にLaunchedEffectを仕込んだときにどんな感じになるのかを知りたかったので実験!

@Composable
fun Case1() {
    LaunchedEffect(key1 = true) {
        Log.i("Case1", "LaunchedEffect")
    }
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.verticalScroll(
            state = rememberScrollState(),
        )
    ) {
        for (i in 1..30) {
            Box(
                contentAlignment = Alignment.Center,
                modifier = Modifier.padding(
                    vertical = 10.dp,
                )
            ) {
                Log.i("Case1", "Box $i")
                LaunchedEffect(key1 = i) {
                    Log.i("Case1", "LaunchedEffect in Box $i")
                }
                Text(
                    text = "Text $i"
                )
            }
        }
    }
}

実行結果がこちら。

I/Case1: Box 1
I/Case1: Box 2
I/Case1: Box 3
...中略...
I/Case1: Box 28
I/Case1: Box 29
I/Case1: Box 30
I/Case1: LaunchedEffect
I/Case1: LaunchedEffect in Box 1
I/Case1: LaunchedEffect in Box 2
I/Case1: LaunchedEffect in Box 3
...中略...
I/Case1: LaunchedEffect in Box 28
I/Case1: LaunchedEffect in Box 29
I/Case1: LaunchedEffect in Box 30

Columnを使っているので、画面に表示されていないアイテムもコンポーズ化されてLaunchedEffect内の処理が実行されるログになっていますね。LaunchedEffectはCompositionに入場すると実行されるものなので、これは予想通りという方も多いのかも。

Case2: LazyColumnの子要素にLaunchedEffectを仕込んでみる

@Composable
fun Case2() {
    LaunchedEffect(key1 = true) {
        Log.i("Case2", "LaunchedEffect")
    }
    LazyColumn(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(10.dp)
    ) {
        for (i in 1..30) {
            item {
                LaunchedEffect(key1 = i) {
                    Log.i("Case2", "LaunchedEffect in Item $i")
                }
                Box(
                    contentAlignment = Alignment.Center,
                    modifier = Modifier.padding(
                        vertical = 10.dp,
                    )
                ) {
                    Log.i("Case2", "Box $i")
                    LaunchedEffect(key1 = i) {
                        Log.i("Case2", "LaunchedEffect in Box $i")
                    }
                    Text(
                        text = "Text $i"
                    )
                }
            }
        }
    }
}

初期表示時の実行結果がこちら。

I/Case2: Box 1
I/Case2: Box 2
I/Case2: Box 3
...中略...
I/Case2: Box 15
I/Case2: Box 16
I/Case2: Box 17
I/Case2: LaunchedEffect
I/Case2: LaunchedEffect in Item 1
I/Case2: LaunchedEffect in Box 1
I/Case2: LaunchedEffect in Item 2
I/Case2: LaunchedEffect in Box 2
I/Case2: LaunchedEffect in Item 3
I/Case2: LaunchedEffect in Box 3
...中略...
I/Case2: LaunchedEffect in Item 15
I/Case2: LaunchedEffect in Box 15
I/Case2: LaunchedEffect in Item 16
I/Case2: LaunchedEffect in Box 16
I/Case2: LaunchedEffect in Item 17
I/Case2: LaunchedEffect in Box 17

画面に表示されているアイテムまでがコンポーズされ、それに伴うLaunchedEffectが実行されていますね。
下にスクロールしていくと、ビューポートに表示されたアイテムが次々とコンポーズ化して配置されていき、同じくLaunchedEffectが実行されています。

I/Case2: Box 27
I/Case2: LaunchedEffect in Item 27
I/Case2: LaunchedEffect in Box 27
I/Case2: Box 28
I/Case2: LaunchedEffect in Item 28
I/Case2: LaunchedEffect in Box 28
I/Case2: Box 29
I/Case2: LaunchedEffect in Item 29
I/Case2: LaunchedEffect in Box 29
I/Case2: Box 30
I/Case2: LaunchedEffect in Item 30
I/Case2: LaunchedEffect in Box 30

上へスクロールしていくと、Compositionから退場していたアイテムがCompositionに入場するので下記のようなログになりました!(LaunchedEffectのキーはインデックスで固定しているので、キーに変化がなくてもこのような結果になっています。)

I/Case2: Box 3
I/Case2: LaunchedEffect in Item 3
I/Case2: LaunchedEffect in Box 3
I/Case2: Box 2
I/Case2: LaunchedEffect in Item 2
I/Case2: LaunchedEffect in Box 2
I/Case2: Box 1
I/Case2: LaunchedEffect in Item 1
I/Case2: LaunchedEffect in Box 1

LazyColumnのアイテムはRecyclerViewのように使い回しはされずに、画面に表示されたらCompositionに入場し、画面外にいったらCompositionから退場する。そのため、1つ1つのアイテム内に書いたLaunchedEffectの処理は、画面に表示されるたびに実行されるということなんですね💡

Case3: LazyColumnの子要素をComposable関数に切り出したうえで、LaunchedEffectを仕込んでみる

一応、別の関数に分けたらどうなるんだろう?と気になったので。

@Composable
fun Case3() {
    LaunchedEffect(key1 = true) {
        Log.i("Case3", "LaunchedEffect")
    }
    LazyColumn(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.spacedBy(10.dp)
    ) {
        for (i in 1..30) {
            item(key = i) {
                Case3Item(position = i)
            }
        }
    }
}

@Composable
fun Case3Item(position: Int) {
    LaunchedEffect(key1 = position) {
        Log.i("Case3", "LaunchedEffect in Item $position")
    }
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier.padding(
            vertical = 10.dp,
        )
    ) {
        Log.i("Case3", "Box $position")
        LaunchedEffect(key1 = position) {
            Log.i("Case3", "LaunchedEffect in Box $position")
        }
        Text(
            text = "Text $position"
        )
    }
}

実行結果はCase2と全く同じでした(なので省略)!

まとめ

LazyColumnの各アイテム内にLaunchedEffectを書くと表示されるたびに実行されるというのはとても興味深かった…⛄️

LaunchedEffectをどこに書くか?という点に関しては、どのコンポーザブルがCompositionに入場したときに実行されてほしいかみたいなところなんだろうな〜。書く場所によって意味も違ってくるよねという感じでした。
もし、LazyColumn内の1つのアイテムにのみ関係するLaunchedEffectは私だったらどこに書くかな?とちょっと考えてみたんですが、個人的には近くに置いておきたいかな〜って感じでした。
表示されるたびに実行されるじゃん!と悩んだのですが、もしアイテム外にLaunchedEffectを出した場合には該当アイテムが表示されてないときにLaunchedEffect内の処理が実行されることになるので「まあ、それならどっちもどっちなのかな〜。なんかカクツクなって思ったら、その時に対応すればいいのかな〜。」という結論に至りました🏋️‍♀️✨

たいしたコードじゃないけれど、実験用コード置いておきます。

この記事が気に入ったらサポートをしてみませんか?