StateFlow: горячий контейнер состояния для интерфейса
Используйте StateFlow как реактивный контейнер состояния и наблюдайте за ним в ViewModels.
«StateFlow: горячий контейнер состояния для интерфейса» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.
Что такое StateFlow?
StateFlow — это горячий поток, хранящий состояние, который всегда содержит значение и передаёт обновления всем подписчикам. В современных архитектурах Kotlin он заменяет LiveData.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val stateFlow = MutableStateFlow(0) // initial value
println("Current: ${stateFlow.value}")
stateFlow.value = 42
println("Updated: ${stateFlow.value}")
}MutableStateFlow и StateFlow
MutableStateFlow — изменяемая реализация, используемая внутри. Предоставляйте внешним наблюдателям доступ только для чтения через приведение типа или с помощью .asStateFlow().
import kotlinx.coroutines.flow.*
class CounterViewModel {
private val _count = MutableStateFlow(0) // mutable internally
val count: StateFlow<Int> = _count.asStateFlow() // read-only externally
fun increment() { _count.value++ }
fun decrement() { _count.value-- }
}Сбор значений StateFlow
Собирайте значения StateFlow так же, как значения любого другого потока. Последнее значение передаётся сразу при подписке.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val state = MutableStateFlow("loading")
launch {
state.collect { println("State: $it") }
}
delay(50); state.value = "success"
delay(50); state.value = "idle"
delay(50)
coroutineContext.cancelChildren()
}StateFlow объединяет одинаковые значения
StateFlow выдаёт значение только при его изменении. Если установить одно и то же значение дважды, будет выполнена только одна выдача — последовательные дубликаты объединяются.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val flow = MutableStateFlow("A")
launch {
flow.collect { println("Received: $it") }
}
delay(50)
flow.value = "A" // no emission — same value
flow.value = "B" // emits
flow.value = "B" // no emission — same value
flow.value = "C" // emits
delay(50)
coroutineContext.cancelChildren()
}StateFlow в ViewModel
Канонический шаблон ViewModel: закрытый MutableStateFlow изменяется бизнес-логикой, а открытый StateFlow наблюдается пользовательским интерфейсом.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
sealed class UiState { object Loading : UiState(); data class Success(val data: String) : UiState(); data class Error(val msg: String) : UiState() }
class MyViewModel(scope: CoroutineScope) {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
init {
scope.launch {
delay(100) // simulate load
_uiState.value = UiState.Success("Hello!")
}
}
}Атомарное изменение состояния
Используйте update { }, чтобы атомарно обновить состояние на основе его текущего значения. Это важно при одновременных обновлениях.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val counter = MutableStateFlow(0)
List(100) {
launch(Dispatchers.Default) {
counter.update { it + 1 } // thread-safe atomic update
}
}.forEach { it.join() }
println("Counter: ${counter.value}") // 100
}compareAndSet для оптимистичных обновлений
compareAndSet(expect, update) устанавливает новое значение только тогда, когда текущее значение совпадает с expect. Это полезно для оптимистического управления параллельным доступом.
import kotlinx.coroutines.flow.*
fun main() {
val state = MutableStateFlow("idle")
val changed = state.compareAndSet("idle", "loading")
println("Changed: $changed, Value: ${state.value}") // true, loading
val changed2 = state.compareAndSet("idle", "error") // won't change
println("Changed: $changed2, Value: ${state.value}") // false, loading
}StateFlow и LiveData
StateFlow работает независимо от жизненного цикла Android. LiveData учитывает жизненный цикл, но доступен только в Android. Предпочитайте StateFlow для общих KMP ViewModels.
import kotlinx.coroutines.flow.*
// StateFlow: pure Kotlin, works in commonMain
// val state = MutableStateFlow("value")
// LiveData: Android-only, lifecycle-aware
// val liveData = MutableLiveData("value")
// In Compose, collect StateFlow with:
// val state by viewModel.uiState.collectAsState()
fun main() { println("StateFlow: multiplatform; LiveData: Android-only") }stateIn: преобразование холодного потока в StateFlow
flow.stateIn(scope, started, initialValue) преобразует холодный поток в горячий StateFlow, используя одну подписку для всех подписчиков.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val cold = flow {
println("Fetching...")
delay(100)
emit("data")
}
val hot: StateFlow<String> = cold.stateIn(
scope = this,
started = SharingStarted.Lazily,
initialValue = "loading"
)
launch { hot.collect { println("A: $it") } }
launch { hot.collect { println("B: $it") } }
delay(200)
coroutineContext.cancelChildren()
}Стратегии SharingStarted
Сразу: запускается немедленно. По требованию: запускается при появлении первого подписчика. WhileSubscribed(stopTimeout): останавливается, когда подписчиков нет, и перезапускается при появлении подписчика.
import kotlinx.coroutines.flow.*
// Eagerly: upstream starts right away
// SharingStarted.Eagerly
// Lazily: waits for first subscriber
// SharingStarted.Lazily
// WhileSubscribed: stops 5s after last subscriber
// SharingStarted.WhileSubscribed(5000)
// Typical ViewModel usage:
// val uiState = repo.dataFlow.stateIn(viewModelScope, WhileSubscribed(5000), Loading)Наблюдение за производным состоянием
Получите новый StateFlow из существующего с помощью map + stateIn. Производный поток обновляется при каждом изменении исходного.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val count = MutableStateFlow(5)
val doubled = count.map { it * 2 }.stateIn(this, SharingStarted.Eagerly, 10)
println(doubled.value) // 10
count.value = 7
delay(50)
println(doubled.value) // 14
coroutineContext.cancelChildren()
}Быстрая проверка
Что делает StateFlow, если дважды установить одно и то же значение?
Повторение
StateFlow — горячий поток, всегда содержащий значение и объединяющий одинаковые последовательные значения. Используйте MutableStateFlow внутри и предоставляйте снаружи StateFlow. Для одновременных изменений используйте атомарное обновление, а для преобразования холодных потоков — stateIn().
Часто задаваемые вопросы
Урок «StateFlow: горячий контейнер состояния для интерфейса» бесплатный?
Да — полный текст урока «StateFlow: горячий контейнер состояния для интерфейса» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.
Чему я научусь в уроке «StateFlow: горячий контейнер состояния для интерфейса»?
Используйте StateFlow как реактивный контейнер состояния и наблюдайте за ним в ViewModels. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Kotlin Academy?
Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «StateFlow: горячий контейнер состояния для интерфейса»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Kotlin Academy?
Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- StateFlow: горячий контейнер состояния для интерфейса
- SharedFlow: шины событий и одноразовые события
- Преобразование холодного Flow в горячий с shareIn и stateIn
- Тестирование StateFlow и SharedFlow с Turbine