Почему второй одинаковый error-toast не доходит до UI через этот StateFlow?
View model публикует одноразовые UI-события через StateFlow. Первое неудачное сохранение показывает toast. Пользователь повторяет попытку, сохранение падает снова с тем же самым текстом — и toast не появляется вовсе. Коллектор жив, другие экраны обновляются нормально.
Ограничение: исправление должно сохранять каждое событие, включая два одинаковых подряд, и не вводить ручной флаг «событие обработано» на объекте события.
class SaveViewModel : ViewModel() {
private val _events = MutableStateFlow<UiEvent?>(null)
val events: StateFlow<UiEvent?> = _events
fun onSaveFailed() {
_events.value = UiEvent.Toast("Save failed")
}
}
Найдите и исправьте ошибку.
StateFlow склеивает эмиссии и ведёт себя так, будто на нём стоит distinctUntilChanged, поэтому присваивание значения, равного текущему, не испускает ничего — второй одинаковый Toast отбрасывается до коллектора. StateFlow моделирует состояние, а не события: публикуйте их через MutableSharedFlow и emit.
- ✗Использовать
StateFlowдля событий и винить коллектор, когда дубликат пропадает - ✗Забывать, что
StateFlowотбрасывает значение, равное уже хранимому - ✗Латать потерю ручным флагом обработки вместо
SharedFlowдля событий
- →Почему
MutableSharedFlowсreplay = 0доставляет два одинаковых события? - →Что сделает
extraBufferCapacity = 0сemit, когда коллектор медленный?
Что происходит
MutableStateFlow — это conflated state holder. Его сеттер value сравнивает новое значение со старым через equals и, если они равны, не испускает ничего: StateFlow ведёт себя так, будто на нём всегда стоит distinctUntilChanged.
UiEvent.Toast("Save failed") — data class, поэтому второй Toast с тем же текстом equals первому. Присваивание проходит, эмиссии нет, toast не показывается.
Исправление
class SaveViewModel : ViewModel() {
private val _events = MutableSharedFlow<UiEvent>(
replay = 0,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
val events: SharedFlow<UiEvent> = _events.asSharedFlow()
fun onSaveFailed() {
_events.tryEmit(UiEvent.Toast("Save failed"))
}
}
SharedFlow не хранит текущего значения и не сравнивает эмиссии между собой — два равных события доставляются оба. replay = 0 не даёт событию воспроизвестись после поворота экрана.