Flow — это упакованный в объект фрагмент кода, который производит какой-то последовательный набор элементов (данных). С одной стороны есть условный Отправитель, который этот код создал и готов предоставить его в виде Flow обертки. А с другой стороны есть Получатель, который возьмет этот Flow, запустит его и предоставит ему специальный колбэк, куда Flow будет складывать данные, который он производит.
Если вы знаете RxJava, то Flow — это аналог Observable/Flowable. Т.е. это некая фабрика, которая умеет производить данные. При создании она ничего не делает. Но как только мы ее запустим, она начнет работу и будет постить результаты в колбэк, который мы ей предоставим.
Flows are cold
Вычисления данных для Flow обычно холодные (cold) — Flow, созданный билдером flow {…}, является пассивной сущностью. Рассмотрим следующий код:
val coldFlow = flow {
while (isActive) {
emit(nextEvent)
}
}
val coldFlow = flow {
while (isActive) {
emit(nextEvent)
}
}
Сами Flow не начинают вычисляться и не хранят состояния пока на них не подпишется collector. Каждая корутина с collector-ом создает новый экземпляр кода, упаковывающего данные во Flow.
Но что насчет таких событий, как действия пользователя, события из операционной системы от датчиков устройства или о изменении состояния? Они появляются независимо от того, есть ли сейчас какой-либо collector, который в них потенциально заинтересован. Они также должны поддерживать нескольких collectors внутри приложения. Это так называемые горячие источники данных…
Shared flows
Вот здесь-то и появляется концепция SharedFlow. SharedFlow существует независимо от того, есть-ли сейчас collectors или нет. Collector у SharedFlow называется подписчиком (observer). Все observers получают одинаковую последовательность значений. Он работает как BroadcastChannel, но эффективнее и делает концепцию BroadcastChannel устаревшей.
SharedFlow — это легковесная широковещательный event bus, который вы можете создать и использовать в своей архитектуре приложения.
Все observers SharedFlow асинхронно собирают данные в своем собственном coroutine context. Emmiter не ждет, пока подписчики закончат обработку данных. Однако, когда общий буфер SharedFlow заполнен, emmiter приостанавливается, пока в буфере не появится место. Альтернативные стратегии работы с переполненным буфером настраиваются параметром BufferOverlow.
В SharedFlow события транслируются неизвестному количеству (⩾0) подписчиков. При отсутствии подписчика любое опубликованное событие немедленно удаляется. Это шаблон проектирования можно использовать для событий, которые должны обрабатываться немедленно или не обрабатываться вообще.
State flows
Частый способ справиться с переполнением буфера — отбрасывать старые данные и сохранять только новые. В частности, при единичном размере буфера мы имеем дело со state variable. Это настолько распространенный вариант использования, что у него есть собственный специализированный тип — StateFlow.
Смотрите на StateFlow как на изменяемую(мутабельную) переменную, на изменения которой можно подписаться. Его последнее значение всегда доступно, и, фактически, последнее значение — единственное, что важно для observers.