When Should You Use snapshotFlow in Jetpack Compose?

Jetpack Compose provides several ways to react to state changes. Among them, snapshotFlow is often misunderstood.
Many developers either overuse it or never use it at all.
The key idea is simple:
Use
snapshotFlowwhen you need to convert Compose state into a Kotlin Flow and perform side effects.
If you’re only updating the UI, snapshotFlow is usually the wrong tool.
What snapshotFlow Actually Does
snapshotFlow observes Compose state and emits new values as a Flow whenever that state changes.
snapshotFlow {
state.value
}
You can then use all Flow operators such as:
debouncefiltermapcombinedistinctUntilChanged
This makes it ideal for reacting to UI state changes with asynchronous operations.
Compose State
↓
snapshotFlow
↓
Flow
↓
Flow Operators
↓
Side Effects
The Most Common Use Case: Scroll Tracking
This is probably the most common real-world use of snapshotFlow.
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.distinctUntilChanged()
.collect { index ->
analytics.trackScroll(index)
}
}
Instead of updating UI, we’re observing scroll position and sending analytics events.
Other examples include:
- Showing a “Back to Top” button
- Infinite scrolling
- Logging user behavior
- Triggering data prefetching
Debounced Search
Another popular pattern is search input handling.
Without debounce, every keystroke could trigger a network request.
LaunchedEffect(Unit) {
snapshotFlow { query }
.debounce(500)
.distinctUntilChanged()
.collect {
viewModel.search(it)
}
}
This combines Compose state with Flow operators naturally.
Monitoring Pager Changes
For paged UIs, you may want to know when the user navigates to a different page.
LaunchedEffect(pagerState) {
snapshotFlow { pagerState.currentPage }
.distinctUntilChanged()
.collect {
analytics.pageViewed(it)
}
}
This is useful for:
- Analytics
- Page impression tracking
- Lazy loading content
- Prefetching data
Monitoring Bottom Sheet State
LaunchedEffect(sheetState) {
snapshotFlow { sheetState.currentValue }
.collect {
analytics.logSheetState(it)
}
}
Whenever the sheet expands, collapses, or changes state, the Flow emits a new value.
When derivedStateOf Is Better
A common mistake is using snapshotFlow for UI calculations.
Suppose you only want to show a button when the list is scrolled.
val showButton by remember {
derivedStateOf {
listState.firstVisibleItemIndex > 0
}
}
This is a UI concern.
No Flow is required.
No side effects are involved.
derivedStateOf is simpler and more efficient.
snapshotFlow vs derivedStateOf
| Purpose | derivedStateOf | snapshotFlow |
|---|---|---|
| UI state calculation | ✅ | ❌ |
| Side effects | ❌ | ✅ |
| Analytics | ❌ | ✅ |
| Debounce | ❌ | ✅ |
| Flow operators | ❌ | ✅ |
| API requests | ❌ | ✅ |
A useful rule of thumb is:
Need to update UI?
→ derivedStateOf
Need a Flow?
→ snapshotFlow
A Common Anti-Pattern
Avoid using snapshotFlow just to update Compose state.
snapshotFlow { state }
.collect {
text = it
}
Compose already observes state changes and automatically recomposes the UI.
Adding a Flow in the middle only increases complexity.
snapshotFlow vs LaunchedEffect
Many developers wonder whether they should use LaunchedEffect or snapshotFlow.
The answer depends on whether Flow operators are needed.
If you simply want to react immediately when a value changes, LaunchedEffect is usually enough.
LaunchedEffect(selectedTab) {
loadData(selectedTab)
}
However, if you need operators such as debounce, filter, or distinctUntilChanged, snapshotFlow becomes a better fit.
LaunchedEffect(Unit) {
snapshotFlow { selectedTab }
.debounce(300)
.distinctUntilChanged()
.collect {
loadData(it)
}
}
A simple guideline is:
Immediate reaction
↓
LaunchedEffect
Flow operators needed
↓
snapshotFlow
Conclusion
snapshotFlow is not a replacement for Compose state management.
Instead, think of it as a bridge between Compose state and Kotlin Flow.
Use it when:
- Observing scroll position
- Debouncing search input
- Tracking analytics
- Monitoring pager changes
- Reacting to state changes with asynchronous work
If your goal is simply to update the UI, derivedStateOf is often the better choice.
The simplest mental model is:
UI calculation
↓
derivedStateOf
Side effects + Flow operators
↓
snapshotFlow
Once you view snapshotFlow as a tool for side effects rather than UI state, its use cases become much clearer.