📷

[Jetpack Compose] snapshotFlow の解説

2023/07/20に公開

はじめに

自分が調べたもののまとめです。

snapshotFlow の簡単なまとめ

  • State の変更を監視するのに使う。
  • State<T> の特定の属性の変更のみを監視する使い方もできる(その使い方がメイン?)
  • 内部的に Snapshot が利用されている

サンプル1

簡単な使い方の例

snapshotFlow の引数の block で参照している State (例での counter)が監視対象になる。

import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow

@Composable
fun SnapshotFlowSample() {
    var counter = remember { mutableStateOf(0) }

    LaunchedEffect(counter) {
        snapshotFlow { counter }
            .collect { // it: MutableState<Int>
                Log.d("App", "collected ${it.value}")
            }
    }

    Column() {
        Button(
            onClick = { counter.value = counter.value + 1 }
        ) {
            Text("countUp")
        }
    }
}

サンプル2

ブロックで返却する値は T の属性でもよい。その場合、返却値に変更があった場合のみ emit される。

import android.util.Log
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow

@Composable
fun SnapshotFlowSample() {
    val pairCounter = remember { mutableStateOf(Pair(0, 0)) }

    LaunchedEffect(pairCounter) {
        // 第一項( first ) のみ監視する
        snapshotFlow { pairCounter.value.first }
            .collect {// it: Int
                Log.d("App", "collected $it")
            }
    }

    Column {
        Button(
            onClick = { pairCounter.value = pairCounter.value.copy(first = pairCounter.value.first + 1)}
        ) {
            Text("countUp first")
        }

        Button(
            onClick = { pairCounter.value = pairCounter.value.copy(second = pairCounter.value.second + 1)}
        ) {
            Text("countUp second")
        }
    }
}

実行結果

collected 0
collected 1
collected 2
// second の変更は emit されない

よくある使い方

LazyListState.firstVisibleItemIndex を snapshotFlow で監視し、リストの要素のどこまでが表示されたかを分析・トラッキングするのに利用するのが一般的というか、典型的な例。

(おまけ) Snapshot

ある時点での全ての状態のスナップショットをとるもの

  • snapshot に対して、その snapshot からの状態の変更を監視する仕組みがある( Snapshot.registerApplyObserver など)。

    • snapshotFlow では

      1. 全ての状態の変更を監視し
      2. snapshotFlow で参照した状態の変更が発生した場合
      3. block を再評価して返却値が変化した場合に emit

      ということが行われている(っぽい)

  • Snapshot は jetpack compose の基礎部分である状態の変更を検知して recomposition が行われる仕組みに利用されている(っぽい)。

参考

Discussion