In modern Android development, managing UI state and one-time events correctly is critical for building scalable, bug-free applications. With Kotlin Coroutines, developers now rely heavily on Flow, especially StateFlow and SharedFlow.
However, confusion between StateFlow vs SharedFlow is one of the most common causes of:
- Duplicate navigation
- Snackbar re-triggering on rotation
- Inconsistent UI behavior
- Fragile ViewModels
This article provides a clear, practical, production-ready explanation of StateFlow vs SharedFlow in Android, including real use cases, best practices, and anti-patterns.
Why StateFlow vs SharedFlow Matters in Android
Android UI is driven by two fundamentally different concepts:
- UI State – what the screen should display
- UI Events – actions that should happen once
Trying to represent both using a single mechanism leads to architectural instability.
This is exactly why StateFlow and SharedFlow exist as separate tools.
What Is StateFlow in Android?
StateFlow is a hot Flow designed to hold and expose UI state.
Key characteristics of StateFlow
- Always has an initial value
- Always holds the latest state
- Replays the latest value to new collectors
- Perfect for UI rendering
- Configuration-change safe
Typical StateFlow use cases
- Screen UI state
- Loading / success / error states
- Form input values
- ViewModel state containers
Example: StateFlow in a ViewModel
data class LoginUiState(
val isLoading: Boolean = false,
val error: String? = null
)
private val _uiState = MutableStateFlow(LoginUiState())
val uiState = _uiState.asStateFlow()
If the Activity or Fragment is recreated, the UI immediately renders correctly.
This is the defining feature of StateFlow.
What Is SharedFlow in Android?
SharedFlow is a hot Flow for events, not state.
Key characteristics of SharedFlow
- No required initial value
- Emits values independently of collectors
- Can be configured for replay and buffering
- Designed for one-time events
Typical SharedFlow use cases
- Navigation events
- Snackbars and Toasts
- Dialog triggers
- One-time error messages
- Analytics events
Example: SharedFlow for UI events
private val _events = MutableSharedFlow<UiEvent>(
replay = 0,
extraBufferCapacity = 1
)
val events = _events.asSharedFlow()
If the UI is not collecting at the moment of emission, the event is intentionally lost.
This behavior is correct and desirable for one-time actions.
StateFlow vs SharedFlow: Side-by-Side Comparison
| Feature | StateFlow | SharedFlow |
|---|---|---|
| Holds UI state | Yes | No |
| Handles one-time events | No | Yes |
| Requires initial value | Yes | No |
| Replays values | Latest only | Configurable |
| UI renders from it | Yes | No |
| Rotation-safe rendering | Yes | Not applicable |
| Replaces LiveData | Yes | Replaces SingleLiveEvent |
The Golden Rule
If the UI needs the value to draw itself → use StateFlow
If the UI needs to react once → use SharedFlow
This rule alone eliminates most Android UI bugs related to Flow.
Using StateFlow and SharedFlow Together (Best Practice)
In real Android applications, you almost always use both.
Production-ready ViewModel example
class LoginViewModel : ViewModel() {
private val _uiState = MutableStateFlow(LoginUiState())
val uiState = _uiState.asStateFlow()
private val _events = MutableSharedFlow<UiEvent>()
val events = _events.asSharedFlow()
fun login() {
_uiState.value = _uiState.value.copy(isLoading = true)
viewModelScope.launch {
// business logic
_events.emit(UiEvent.NavigateToHome)
}
}
}
- StateFlow → UI rendering
- SharedFlow → UI effects
- Clean separation
- Predictable behavior
- Easy testing
Common Mistakes to Avoid
Using SharedFlow for UI state
Leads to missed values and broken UI rendering.
Using StateFlow for navigation
Causes navigation to trigger again on rotation.
Setting replay > 0 for events
Re-introduces the exact bugs SharedFlow was created to fix.
Mixing state and events in one Flow
Destroys clarity and testability.
SharedFlow vs LiveData vs SingleLiveEvent
LiveData→ legacy, lifecycle-boundSingleLiveEvent→ workaround with known issues- SharedFlow → coroutine-native, explicit, testable
In modern Android development, SharedFlow is the correct replacement for SingleLiveEvent.
Final Thoughts: Which One Should You Use?
- Use StateFlow for UI state
- Use SharedFlow for one-time events
- Never mix responsibilities
- Design ViewModels with clear intent
This separation scales cleanly across:
- MVVM
- MVI
- Jetpack Compose
- Multi-module architectures

