Some outputs are not state: a snackbar message, a navigation command, a "copied" toast. If you store "show the message Added Salt and Saffron" in a StateFlow, it shows again after every rotation, because a StateFlow replays its value to each new collector. The two usual tools behave differently:
| Tool | Delivery | When nobody is collecting |
|---|---|---|
| MutableSharedFlow() (no replay) | to every current collector | the event is dropped |
| Channel(BUFFERED) + receiveAsFlow() | to exactly one collector | buffered until collected |
For a snackbar, a lost event is a bug and a doubled one is too, so BookNest uses a Channel:
private val _messages = Channel<String>(Channel.BUFFERED)
val messages: Flow<String> = _messages.receiveAsFlow() // each message delivered once
...
val snackbar = remember { SnackbarHostState() }
LaunchedEffect(cartVm) {
cartVm.messages.collect { snackbar.showSnackbar(it) } // each message shown once
}onEvent sends with trySend, which never suspends. A rotation cancels the LaunchedEffect and the new composition starts another; a message sent in between waits in the channel. Unidirectional Data Flow's figure shows the result.
Google's architecture guidance even models such messages as state (a userMessage field the UI clears with an event once shown). Both approaches are common; pick one per project.