Don’t forget to share it with your network!
Suryaprakash Narsinghbhai Sharma
Sr Developer, Softices
Mobile Development
24 July, 2026
Suryaprakash Narsinghbhai Sharma
Sr Developer, Softices
When Kotlin Coroutines introduced Flow, it changed the way Android developers handle asynchronous data. Instead of relying on callbacks or older reactive APIs, developers gained a simpler, more structured way to work with data.
Later, Kotlin introduced StateFlow and SharedFlow as modern alternatives to many use cases previously handled by LiveData, BroadcastChannel, and ConflatedBroadcastChannel.
One of the most common questions Android developers ask is:
// Should I use StateFlow or SharedFlow?
Although they belong to the same Flow family and have similar APIs, they solve very different problems. Choosing the wrong one can lead to bugs such as duplicate Snackbars, repeated navigation events, or inconsistent UI state.
In this article, we'll explore the differences between StateFlow and SharedFlow, understand when to use each one, and look at practical Android examples.
A StateFlow represents a state that always has a current value.
Think of it as a data holder that continuously exposes the latest UI state. Any new collector immediately receives the current value, making it ideal for representing data that the UI should always display.
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> = _counter
fun increment() {
_counter.value++
}
Collecting the state:
lifecycleScope.launch {
viewModel.counter.collect { count ->
textView.text = count.toString()
}
}
If the screen rotates, the collector instantly receives the latest counter value, ensuring the UI remains consistent.
Imagine a Profile screen.
Loading… → User loaded → User updated
The screen should always display the latest user information.
data class User(
val name: String,
val age: Int
)
private val _user = MutableStateFlow<User?>(null)
val user = _user.asStateFlow()
Whenever the repository updates the user:
_user.value = updatedUser
Even if another collector starts later such as after a configuration change, it immediately receives the latest user object.
This is exactly what StateFlow is designed for.
A SharedFlow represents events instead of state.
Unlike StateFlow, it doesn't require an initial value. Instead of storing the latest UI state, it broadcasts events to active collectors.
Typical examples include:
private val _events = MutableSharedFlow<String>()
val events = _events.asSharedFlow()
suspend fun showMessage() {
_events.emit("Saved Successfully")
}
Collecting:
lifecycleScope.launch {
viewModel.events.collect {
Toast.makeText(
this@MainActivity,
it,
Toast.LENGTH_SHORT
).show()
}
}
Suppose the user taps the Save button.
User taps Save → Show Snackbar → Done
Showing a Snackbar is not state.
You don't want the Snackbar to appear again after the device rotates because the event has already been handled.
Instead, use SharedFlow.
private val _uiEvents = MutableSharedFlow<UiEvent>()
val uiEvents = _uiEvents.asSharedFlow()
sealed class UiEvent {
data object ShowSuccess : UiEvent()
}
Emit the event:
_uiEvents.emit(UiEvent.ShowSuccess)
Collect it:
viewModel.uiEvents.collect { event ->
when (event) {
UiEvent.ShowSuccess ->
snackbarHostState.showSnackbar(
"Saved Successfully"
)
}
}
The Snackbar appears only once, exactly as intended.
A simple analogy makes the difference easy to remember.
Imagine checking today's temperature.
Current temperature = 28°C
Anyone checking now should always see 28°C.
That's StateFlow.
Now imagine a doorbell ringing.
Doorbell rang.
Someone arriving five minutes later shouldn't hear yesterday's ring.
That's SharedFlow.
Feature |
StateFlow |
SharedFlow |
|---|---|---|
| Holds state | ✅ | ❌ |
| Initial value required | ✅ | ❌ |
| New collectors receive latest value | ✅ | Optional (replay) |
| One-time events | ❌ | ✅ |
| Duplicate emissions | Only when value changes | Yes |
| Best for UI state | ✅ | ❌ |
| Best for navigation | ❌ | ✅ |
| Best for Snackbar | ❌ | ✅ |
| Best for loading state | ✅ | ❌ |
Use StateFlow whenever your UI should always reflect the latest value.
Common examples include:
Example:
data class LoginUiState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false
)
Use SharedFlow for actions that should happen only once.
Examples include:
Example:
sealed class UiEvent {
data object NavigateHome : UiEvent()
data class ShowError(val message: String) : UiEvent()
}
A frequent mistake is using StateFlow for Snackbar or Toast messages.
For example:
private val _message = MutableStateFlow("")
When the device rotates, the collector immediately receives the latest value again.
As a result:
Saved Successfully
appears a second time, even though the user didn't trigger another save.
The correct approach is:
private val _events = MutableSharedFlow<String>()
Since SharedFlow doesn't retain events by default, the Snackbar is displayed only once.
One powerful feature of SharedFlow is its configurable replay behavior.
MutableSharedFlow<String>(
replay = 1
)
With replay = 1, the latest event is replayed to new collectors.
However, for one-time UI events, the most common configuration is:
MutableSharedFlow<UiEvent>(
replay = 0,
extraBufferCapacity = 1
)
This ensures events are delivered without being replayed after configuration changes.
Whenever you're deciding between the two, ask yourself a simple question:
|| Is this state or an event?
This simple rule covers the vast majority of Android use cases.
asStateFlow() and
asSharedFlow().
MutableStateFlow and
MutableSharedFlow private inside the ViewModel.
repeatOnLifecycle() for Views or
collectAsStateWithLifecycle() in Jetpack Compose to make flow
collection lifecycle-aware.
UiState object whenever possible
to simplify state management.
Although StateFlow and SharedFlow belong to the same Flow family, they serve fundamentally different purposes.
When in doubt, remember this simple rule:
// State is something you have. Event is something that happens.
Choosing the right flow for the right job will make your Android apps easier to maintain, easier to test, and more resilient as they grow.