TGViewer
Kotlin Kotlin @kotlin_lib · 2.05K subscribers
Post #702 802
🧨 SharedFlow для Side Effects - это ошибка. Разбираем официально лучший подход

В MVI/MVVM есть вечная проблема: разовые события (Side Effects). Показать Snackbar, перейти на другой экран, проиграть звук. Это не стейт, это событие, которое должно произойти ровно один раз.

Долгое время комьюнити пыталось решить это через SharedFlow.
Выглядело это так:


private val _effects = MutableSharedFlow<Effect>()
val effects = _effects.asSharedFlow()


Почему это выстрел в ногу?
По дефолту SharedFlow не имеет буфера (replay = 0). Если ViewModel пушит событие (_effects.emit(...)), когда UI находится в фоне (пользователь свернул приложение или открыл другой экран), у потока в этот момент нет активных подписчиков.
Результат? Событие просто дропается. Улетает в пустоту. Юзер разворачивает апп - а перехода или сообщения об ошибке нет.

Как это обычно пытаются костылить?
Начинают играться с буфером: replay = 1.
Событие перестает теряться, но появляется новая проблема: оно кэшируется! Пользователь видит ошибку, поворачивает экран, UI переподписывается на SharedFlow, считывает кэш, и старый Snackbar показывается второй раз.

✅ Как правильно? (Рекомендация Google)

Использовать Channel.
Каналы созданы именно для паттерна Producer-Consumer и имеют встроенный буфер из коробки.


class ProfileViewModel : ViewModel() {

// 1. Создаем канал
private val _effects = Channel<Effect>()

// 2. Отдаем наружу как холодный Flow
val effects = _effects.receiveAsFlow()

fun saveProfile() {
viewModelScope.launch {
repository.save()
// 3. Отправляем событие. Если UI в фоне — оно ложится в буфер.
_effects.send(Effect.ShowSnackbar("Сохранено"))
}
}
}


Почему Channel - это идеальное решение:

1. Guarantee Delivery: Если UI ушел в фон (подписчиков нет), Channel просто ставит событие в буфер ожидания.

2. Exactly-Once: Как только UI возвращается (например, Fragment снова в onStart) и делает collect, он забирает событие из очереди. Событие доставляется ровно один раз и удаляется из канала.

3. Никаких дублей: При повороте экрана новый collect просто начнет слушать пустой канал. Старое событие уже было "съедено".

Единственный нюанс: на стороне UI нужно собирать такие события правильно, учитывая жизненный цикл (например, через Lifecycle.repeatOnLifecycle или кастомные Compose-эффекты).

✍️ @kotlin_lib
  • 👍 9
More from @kotlin_lib
  1. Aug 26, 2026🚧 Ваш Mutex тормозит корутины. Как перестать лочить и начать жить Вы пишете многопоточный…
  2. Jul 25, 2026🚀 Подборка полезных IT каналов в Max Системное администрирование, DevOps 📌 https://max.r…
  3. Jul 1, 2026🌪 Ваша дата в опасности: flatMapConcat vs Merge vs Latest Вам нужно взять поток ID-шников…
  4. Jun 24, 2026🤖 Android-приложение — это не только красивый экран. За ним стоят работа с внешним API, з…
  5. Jun 23, 2026🔮 Убийца бойлерплейта: Встречайте Context Parameters (Kotlin 2.x) Мы так привыкли к Depen…
  6. Jun 3, 2026Яндекс обновил Yandex Mobile Ads SDK для монетизации мобильных приложений — и это интересн…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →