Что такое 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. Они обеспечивают безопасный и понятный способ управления состоянием и событиями в многопоточной среде.