Summary
This incident examines a subtle but common misuse of MutableSharedFlow in Android/Kotlin applications: using a Boolean emission solely as a navigation trigger. The underlying issue is the assumption that a SharedFlow can “emit nothing,” when in reality it always emits a value, even if that value is meaningless. This postmortem explains why this pattern leads to confusion, how real systems behave, and how senior engineers design cleaner, safer event channels.
Root Cause
The root cause is the use of MutableSharedFlow\<Boolean> as a one‑shot event trigger, even though the Boolean value itself is irrelevant. SharedFlow requires a typed emission, so it cannot emit “nothing.” Developers often misuse Boolean as a placeholder.
Key points:
- SharedFlow always requires a value to emit.
- Using
Booleanas a dummy payload creates semantic noise. - The real intent is an event, not a Boolean state.
Why This Happens in Real Systems
Real systems often evolve under time pressure, and developers reach for the simplest working solution. This leads to:
- Using Boolean as a fake event type because it’s easy.
- Confusing events with state, even though SharedFlow is event‑oriented.
- Lack of awareness that Kotlin provides better primitives for event signaling.
Real-World Impact
This pattern causes several practical issues:
- Ambiguous meaning:
truedoesn’t actually mean anything. - Harder debugging: logs show meaningless values.
- Incorrect mental model: juniors think SharedFlow can emit “nothing.”
- Navigation bugs: replay or lifecycle timing can trigger unwanted navigation.
Example or Code (if necessary and relevant)
A cleaner approach is to use a Unit-based SharedFlow, which represents an event with no payload.
private val _navigateToSecond = MutableSharedFlow()
val navigateToSecond = _navigateToSecond.asSharedFlow()
fun someTask() = viewModelScope.launch {
_navigateToSecond.emit(Unit)
}
And in the Fragment:
viewModel.navigateToSecond.collectOnStarted(viewLifecycleOwner) {
findNavController().navigate(R.id.action_firstFragment_to_secondFragment)
}
How Senior Engineers Fix It
Senior engineers avoid meaningless payloads and instead use semantically correct event types:
- Replace
MutableSharedFlow<Boolean>withMutableSharedFlow<Unit>. - Use Channel when the event must be consumed exactly once.
- Use sealed classes when multiple event types exist.
- Ensure navigation events are one-shot, not replayed state.
Common patterns:
- Unit for simple triggers
- sealed class UiEvent for complex flows
- Channel for strict one-time delivery
Why Juniors Miss It
Juniors often miss this because:
- They assume SharedFlow can emit “nothing.”
- They confuse state with events.
- They use Boolean as a default type without questioning semantics.
- They haven’t yet internalized Kotlin’s Unit type or event-driven patterns.
By understanding the distinction between state, events, and payloads, engineers build cleaner, more maintainable navigation flows.