Что такое StateFlow и SharedFlow?

StateFlow и SharedFlow — это специальные реализации интерфейса Flow в Kotlin Coroutines, предназначенные для горячих (hot) потоков данных, в отличие от классического Flow, который является холодным (cold). Эти два API являются частью библиотеки kotlinx.coroutines.flow и были добавлены, чтобы упростить реактивное программирование и замену LiveData в архитектуре Android.

Общие характеристики SharedFlow и StateFlow

Оба потока:

  • Работают как горячие потоки: они начинают испускать значения независимо от того, есть ли подписчики.

  • Поддерживают несколько подписчиков.

  • Безопасны для многопоточности.

  • Предназначены для использования в архитектуре приложений: ViewModel ↔ UI.

  • Поддерживают операторы collect, map, filter, combine и другие.

StateFlow

StateFlow — это поток, который всегда содержит одно текущее значение. Это горячий аналог LiveData.

Ключевые особенности:

  • Содержит текущее значение (value), которое можно прочитать в любой момент.

  • Подписчик при подключении сразу получает последнее значение.

  • Позволяет легко использовать его в UI, например, для отображения состояния.

  • Основан на SharedFlow с replay = 1.

Инициализация:

val \_state = MutableStateFlow(0)
val state: StateFlow<Int> = \_state

MutableStateFlow используется внутри ViewModel, а наружу отдаётся StateFlow, чтобы запретить внешнюю мутацию.

Изменение значения:

\_state.value = 42

Или:

\_state.update { old -> old + 1 }

Подписка:

lifecycleScope.launch {
viewModel.state.collect { value ->
// Обновить UI
}
}

SharedFlow

SharedFlow — это многократный поток событий, который не хранит текущее состояние, но может повторно воспроизводить (replay) последние значения новым подписчикам.

Ключевые особенности:

  • Не содержит текущего значения (в отличие от StateFlow).

  • Позволяет конфигурировать количество повторов (replay) и буфер.

  • Идеален для одноразовых событий: навигация, показ Snackbar, ошибок, сообщений.

  • Многократные подписчики получают значения одновременно, но только если подписаны в момент события, если replay = 0.

Инициализация:

val \_events = MutableSharedFlow<String>()
val events: SharedFlow<String> = \_events

Отправка события:

viewModelScope.launch {
\_events.emit("Hello!")
}

Подписка:

lifecycleScope.launch {
viewModel.events.collect { event ->
// Показать Toast, Snackbar и т.д.
}
}

Сравнение StateFlow и SharedFlow

Критерий StateFlow SharedFlow
Хранит последнее значение ✅ Да (.value) ❌ Нет
--- --- ---
Используется для Состояния UI Событий (навигация, ошибки и т.д.)
--- --- ---
Поведение при подписке Сразу отдаёт текущее значение Может не отдать ничего (если replay = 0)
--- --- ---
Требует начального значения ✅ Да ❌ Нет
--- --- ---
Многократные подписчики ✅ Да ✅ Да
--- --- ---
Поддерживает emit() ✅ Да ✅ Да
--- --- ---
Поддерживает tryEmit() ✅ Да ✅ Да
--- --- ---
Основан на SharedFlow(replay = 1, extraBuffer = 0) Полностью конфигурируемый
--- --- ---
Используется в UI ✅ Да, отлично для state ✅ Да, но для event
--- --- ---

Поведение StateFlow подробно

StateFlow требует начального значения. Это значение всегда доступно:

val state = MutableStateFlow("initial")
println(state.value) // "initial"

Если никто не подписан — StateFlow всё равно жив и хранит последнее значение.

Сбор начинается как только кто-то вызывает collect:

state.collect { value -> println(value) }

Поведение SharedFlow подробно

SharedFlow не требует начального значения. Оно только вещает значения, когда они поступают.

Можно настроить буфер:

val \_events = MutableSharedFlow<Int>(
replay = 1, // Кол-во последних значений, доступных новым подписчикам
extraBufferCapacity = 2, // Буфер, прежде чем блокировать отправителя
onBufferOverflow = BufferOverflow.DROP_OLDEST // Поведение при переполнении
)

Пример с навигацией:

val \_navigate = MutableSharedFlow<Unit>()
val navigate: SharedFlow<Unit> = \_navigate
fun onButtonClick() {
viewModelScope.launch {
\_navigate.emit(Unit)
}
}

В UI:

lifecycleScope.launch {
viewModel.navigate.collect {
navController.navigate("next_screen")
}
}

StateFlow как замена LiveData

private val \_state = MutableStateFlow<MyState>(MyState.Initial)
val state: StateFlow<MyState> = \_state

И в UI:

lifecycleScope.launchWhenStarted {
viewModel.state.collect { render(it) }
}

Главное отличие от LiveData: StateFlow никогда не становится неактивным. Он не зависит от жизненного цикла и продолжает работать в памяти, пока есть ссылка.

SharedFlow и одноразовые события

Чтобы не терять события при поворотах экрана или многократных подписках, можно использовать SharedFlow с replay = 1:

val \_snackbar = MutableSharedFlow<String>(replay = 1)
val snackbar: SharedFlow<String> = \_snackbar

Тогда даже при пересоздании UI пользователь не пропустит событие.

Расширенные возможности

Оба потока можно преобразовывать и комбинировать с другими Flow:

val transformed: Flow<Int> = state.map { it \* 2 }

Также можно использовать combine, zip, flatMapLatest и другие операторы.

Отличия от LiveData

Особенность StateFlow LiveData
Требует lifecycle ❌ Нет ✅ Да
--- --- ---
Coroutine-friendly ✅ Да ❌ Нет
--- --- ---
Потоковое API ✅ Flow API ❌ Нет
--- --- ---
Имеет value ✅ Да (.value) ✅ Да
--- --- ---
Многопоточность ✅ Полная ⚠️ Частичная
--- --- ---

StateFlow и SharedFlow стали стандартом в архитектуре приложений на Kotlin с корутинами, заменив LiveData, EventBus, RxJava. Они обеспечивают безопасный и понятный способ управления состоянием и событиями в многопоточной среде.