snapshotFlow がなぜCompose専用に用意されているのか、その仕組みと実践的な使い方を紹介します。

Jetpack ComposeにはFlowを扱う機会が数多くあります。
Repositoryからデータを受け取るために Flow を collect したり、ViewModel が公開する StateFlow を collectAsState() したりすることは、すでに日常的なパターンになっています。
一方で、Compose State を逆に Flow へ変換したいと思ったことはないでしょうか。
例えば、
- スクロール位置を監視したい。
- 選択中のタブが変わったことを Analytics へ送信したい。
- TextField の入力を debounce() したい。
- Compose State を Flow の演算子で加工したい。
このような場面で登場するのが snapshotFlow です。
そして実は、Compose StateをFlowへ変換するCompose専用のAPIは snapshotFlow だけです。
🧑🏻💻 なぜflow {}ではダメなのか
最初に思い付くのは、普通の flow ではないでしょうか。
flow {
emit(state.value)
}
もちろんこれは動きます。
しかし、一度値を送信するだけです。
その後 state.value が変化しても、新しい値は流れません。
なぜなら、flow {} はCompose Stateの変更を監視する仕組みを持っていないからです。
🧑🏻💻 snapshotFlow は Compose Snapshot を監視する
snapshotFlow は Compose の Snapshot システムと統合されています。
LaunchedEffect(Unit) {
snapshotFlow {
listState.firstVisibleItemIndex
}.collect { index ->
println(index)
}
}
ラムダ内で読み取った Compose State を Compose 自身が監視し、
値が変化すると新しい値を Flow へ流します。
つまり、自分で emit() を書く必要はありません。
🧑🏻💻 イメージするとこうなる
Compose State
│
▼
Compose Snapshot
│
▼
snapshotFlow
│
▼
Kotlin Flow
│
▼
map / filter / debounce / collect
Compose の世界と Flow の世界をつないでいるのが snapshotFlow です。
🧑🏻💻 Cold Flowであることも重要
snapshotFlow は Cold Flow です。
つまり、
val flow = snapshotFlow {
state.value
}
これだけでは監視は始まりません。
実際に監視が始まるのは collect() された瞬間です。
LaunchedEffect(Unit) {
snapshotFlow {
state.value
}.collect {
// Side Effect
}
}
そのため、Compose では LaunchedEffect と組み合わせて使うのが一般的です。
🧑🏻💻 実践例① スクロール位置を監視する
最もよく使われる例です。
LaunchedEffect(Unit) {
snapshotFlow {
listState.firstVisibleItemIndex
}.collect(viewModel::onScrollChanged)
}
例えば、
- Toolbarの表示・非表示
- FABの表示切り替え
- Analytics送信
などに利用できます。
🧑🏻💻 実践例② Analyticsを送信する
Compose State の変化をイベントとして扱えます。
LaunchedEffect(Unit) {
snapshotFlow {
selectedTab
}.collect(analytics::logTabSelected)
}
UIロジックを汚さず、副作用だけを分離できます。
🧑🏻💻 実践例③ Flow演算子を組み合わせる
snapshotFlow は通常の Flow なので、そのまま演算子を利用できます。
LaunchedEffect(Unit) {
snapshotFlow {
query
}
.debounce(300)
.distinctUntilChanged()
.collect(viewModel::search)
}
Compose Stateを、そのままリアクティブな Flow パイプラインへ接続できます。
🧑🏻💻 snapshotFlow が監視するのは Compose State だけ
重要なのは、監視対象はラムダ内で読み取ったCompose Stateだけという点です。
snapshotFlow {
listState.firstVisibleItemIndex
}
このようなCompose Stateは監視できます。
一方、
var count = 0
snapshotFlow {
count
}
通常の変数は Compose Snapshot が管理していないため、変更しても Flow は新しい値を流しません。
🧑🏻💻 まとめ
Compose にはさまざまな Flow API があります。
しかし、Compose State を Flow へ変換するために設計された Compose 専用 API は snapshotFlow だけです。
その役割は単に State を Flow へ変換することではありません。
Compose Snapshot とKotlin Flow を橋渡しし、
- スクロール監視
- Analytics
- TextField の入力監視
- Flow 演算子との連携
など、Compose で副作用を書くための基盤となるAPIです。
snapshotFlow を理解すると、「Compose State を Flowとして扱う」という考え方が自然になり、Composeらしい副作用の書き方が身に付くはずです。




