0Pricing
Kotlin Academy · Урок

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 — локальная установка не требуется.

Все уроки этого курса

  1. StateFlow: горячий контейнер состояния для интерфейса
  2. SharedFlow: шины событий и одноразовые события
  3. Преобразование холодного Flow в горячий с shareIn и stateIn
  4. Тестирование StateFlow и SharedFlow с Turbine
← Назад к Kotlin Academy