Can MutableSharedFlow Emit Nothing

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 Boolean as 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: true doesn’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> with MutableSharedFlow<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.

Leave a Comment