StateIn
Преобразует холодный Flow в горячий StateFlow, который запускается в заданной области корутин, делая доступным последнее отправленное значение из одного запущенного экземпляра исходного потока для нескольких подписчиков.
Оператор stateIn полезен в ситуациях, когда имеется холодный поток, предоставляющий обновления значения какого-то состояния и являющийся затратным для создания и/или поддержки, но есть несколько подписчиков, которым необходимо собирать самое последнее значение состояния. Например, представьте поток обновлений состояния, поступающих из бэкэнда через дорогостоящее сетевое соединение, требующее много времени для установки. Концептуально это может быть реализовано следующим образом:
val backendState: Flow<State> = flow {
connectToBackend() // takes a lot of time
try {
while (true) {
emit(receiveStateUpdateFromBackend())
}
} finally {
disconnectFromBackend()
}
}
Если этот поток используется непосредственно в приложении, то каждый раз, когда его собирают, создается новое соединение, и прежде чем обновления состояния начнут поступать, потребуется некоторое время. Однако мы можем сделать так, чтобы использовалось единственное соединение, и инициализировать его заранее следующим образом:
val state = backendMessages.stateIn(scope, SharingStarted.Eagerly, State.LOADING)
Теперь единственное соединение разделяется между всеми получателями состояния, и существует вероятность, что соединение уже установлено к моменту его необходимости.
Нормальное завершение исходного потока не влияет на подписчиков, и общая корутина продолжает работу. Если используется стратегия, такая как SharingStarted.WhileSubscribed, то исходный поток может быть перезапущен снова. Если требуется особое действие при завершении исходного потока, то можно использовать оператор onCompletion перед оператором stateIn для отправки особого значения в этом случае.
Любое исключение в исходном потоке завершает общую корутину, не влияя на подписчиков, и будет обработано областью, в которой запущена общая корутина. Пользовательская обработка исключений может быть настроена с использованием операторов catch или retry перед оператором stateIn, аналогично оператору shareIn.
fun <T> MutableStateFlow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted = SharingStarted.WhileSubscribed(),
initialValue: T
): StateFlow<T>
Вы можете использовать три различные стратегии обмена для StateFlow:
Поток начинает обмен данными, когда первый сборщик начинает сбор, и останавливается, когда последний сборщик прекращает работу. Это полезно, когда вы хотите делиться состоянием только тогда, когда есть активный сборщик.
Поток начинает обмен данными, когда первый сборщик начинает сбор, и останавливается после указанного периода неактивности (т.е. когда нет активных сборщиков). Это полезно, когда вы хотите делиться состоянием, пока его используют, но останавливаться после некоторого времени, когда он больше не нужен.
Поток начинает обмен данными немедленно и продолжает делиться им бесконечно, независимо от наличия активных сборщиков. Это полезно, когда вы хотите делиться состоянием постоянно.
stateIn
Чтобы использовать stateIn в вашем приложении Android, просто добавьте его к вашему потоку и передайте область корутины, где должен происходить обмен, стратегию обмена и начальное состояние. Вот пример использования stateIn для создания общего StateFlow, который генерирует случайное число каждую секунду:
val randomNumbers = MutableStateFlow(0)
val sharedFlow = randomNumbers.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
initialValue = 0
)
fun generateRandomNumbers() {
viewModelScope.launch {
while (true) {
delay(1000)
randomNumbers.value = (0..100).random()
}
}
}
В этом примере мы создаем новый MutableStateFlow под названием randomNumbers с начальным значением 0. Затем мы используем stateIn, чтобы создать общий объект StateFlow под названием sharedFlow, который разделяет тот же поток randomNumbers. Мы указываем, что обмен должен начинаться, когда есть подписчики на поток, и используем viewModelScope в качестве области корутины.
Объект StateFlow, возвращаемый stateIn, может быть собран несколькими сборщиками, и каждый сборщик будет получать те же обновления от источника сверху. Когда значение randomNumbers изменяется, все сборщики sharedFlow немедленно получают обновленное значение.
Теперь, когда у нас есть общий StateFlow, мы можем создать несколько сборщиков, которые будут получать одни и те же эмиссии. Мы также можем обновлять значение переменной счетчика, и все сборщики получат обновленное значение. Например:
sharedFlow.collect {
Log.d("SharedFlow", it)
}
sharedFlow.collect {
textView.text = it.toString()
}
generateRandomNumbers()
В этом примере мы создаем два сборщика, которые будут получать одни и те же эмиссии из sharedFlow. Первый сборщик записывает каждую эмиссию в консоль, а второй сборщик устанавливает текст TextView для каждой эмиссии. Мы также вызываем функцию generateRandomNumbers(), которая обновляет значение переменной randomNumber и вызывает новые эмиссии.