Перейти к основному содержимому

Flow в Kotlin

Разработчику Архитектору

Flow в Kotlin

Корутина выполняет одну задачу и возвращает один результат. Flowпоследовательность значений во времени: пришла новая строка из БД, обновился счётчик, прилетела порция из сети.

Типичные места:

  • RoomobserveAll(): Flow<List<Note>>;
  • Ktor — стриминг ответов;
  • Compose — collectAsState из StateFlow.

База: корутины · синхронные коллекции: 225.


List, Sequence и Flow — сравнение

ListSequenceFlow
Когда данныеВсё сразу в памятиЛениво, в одном потокеЛениво + suspend
Сеть / БДНужно самому загрузить в ListТо жеМожет эмитить по мере готовности
Повторный запускТот же объектНовый проход итератораХолодный Flow — заново при новом collect
КонтекстОбычный кодОбычный кодКорутины

Словарь терминов

ТерминПростыми словами
emit«Выдать» следующее значение подписчикам Flow.
collectПодписаться и обрабатывать каждое значение (suspend-цикл).
Холодный FlowНачинает работу, когда появился первый подписчик.
Горячий потокЖивёт сам по себе; подписчики получают текущее состояние (StateFlow).
Upstream / downstreamПродюсер → операторы → collect.
BackpressureПродюсер быстрее потребителя — буферы (buffer).

Холодный Flow

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun counter(): Flow<Int> = flow {
for (i in 1..5) {
delay(200)
emit(i)
}
}

suspend fun main() {
counter().collect { println(it) }
}

Разбор:

ЧастьСмысл
flow { }Билдер: внутри можно emit и вызывать suspend-функции.
delay(200)Пауза между числами; поток не блокируется.
collect { }Запускает выполнение; без collect холодный Flow ничего не делает.

Два подряд counter().collect { } выполнят цикл дважды — это норма для холодного потока.


Горячие потоки — StateFlow и SharedFlow

Экрану нужно одно актуальное состояние («загрузка» / «список» / «ошибка»):

private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

fun onDataLoaded(data: List<Item>) {
_uiState.value = UiState.Success(data)
}
ТипКогда
StateFlowСостояние UI, настройки — всегда есть «последнее значение»
SharedFlowСобытия: «показать Snackbar», навигация один раз
channelFlowНесколько продюсеров пишут в один Flow

В Compose: uiState.collectAsStateWithLifecycle() — подписка с учётом жизненного цикла.


Операторы (как у коллекций, но suspend)

repository.observeOrders()
.map { orders -> orders.filter { it.isOpen } }
.distinctUntilChanged()
.catch { e -> emit(emptyList()); log(e) }
.flowOn(Dispatchers.IO)
.collect { openOrders -> render(openOrders) }
ОператорРоль
mapПреобразовать каждое значение
filterОтфильтровать
distinctUntilChangedПропустить, если новое equals предыдущему (меньше перерисовок UI)
catchПоймать ошибку upstream, можно emit запасное значение
flowOn(Dispatchers.IO)Тяжёлую работу upstream выполнять на IO
debounceПодождать паузу ввода (поиск по буквам)
flatMapLatestПри новом значении отменить предыдущую вложенную работу

flatMapLatest типичен для поиска: пользователь напечатал «к» → «ко» → «кот»; запрос за «к» отменяется, когда пришло «ко».


Объединение потоков

val merged = merge(flowA, flowB)

val combined = combine(tempFlow, humidityFlow) { t, h ->
Weather(t, h)
}
  • merge — пришло значение из любого источника.
  • combine — ждёт обновления каждого участника, строит результат из последних пар.

Отмена

collect — suspend-функция. При отмене viewModelScope (уход с экрана) цепочка останавливается, upstream получает CancellationException.

Внутри flow { } блокирующий код без withContext(Dispatchers.IO) может занять поток диспетчера — переносите тяжёлую работу на IO.


Тестирование

@Test
fun emitsFirstThree() = runTest {
val items = counter().take(3).toList()
assertEquals(listOf(1, 2, 3), items)
}

Для пошаговой проверки событий по мере прихода удобна библиотека Turbine — см. экосистему.


Частые ошибки

СимптомПричина
Flow «молчит»Нет collect у холодного Flow
Двойной запрос к APIДва collect без shareIn / кэша
UI дёргаетсяНет distinctUntilChanged на частых обновлениях
ЗависаниеБлокирующий код в flow { } на Main

Дальше

Корутины · Room + Flow · Compose · тесты


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).