StateFlow и SharedFlow — это API для потоков , позволяющие потокам оптимально передавать обновления состояния и значения нескольким потребителям.
StateFlow
StateFlow — это наблюдаемый поток, хранящий состояние, который отправляет текущие и новые обновления состояния своим сборщикам данных. Текущее значение состояния также можно прочитать через его свойство value . Чтобы обновить состояние и отправить его в поток, присвойте новое значение свойству value класса MutableStateFlow .
В Android StateFlow отлично подходит для классов, которым необходимо поддерживать наблюдаемое изменяемое состояние.
Следуя примерам из Kotlin Flows , StateFlow можно предоставить из LatestNewsViewModel , чтобы View могло отслеживать обновления состояния пользовательского интерфейса и, по сути, обеспечивать сохранение состояния экрана при изменении конфигурации.
class LatestNewsViewModel( private val newsRepository: NewsRepository ) : ViewModel() { // Backing property to avoid state updates from other classes private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList())) // The UI collects from this StateFlow to get its state updates val uiState: StateFlow<LatestNewsUiState> = _uiState init { viewModelScope.launch { newsRepository.favoriteLatestNews // Update UI with the latest favorite news // Writes to the value property of MutableStateFlow, // adding a new element to the flow and updating all // of its collectors .collect { favoriteNews -> _uiState.value = LatestNewsUiState.Success(favoriteNews) } } } } // Represents different states for the LatestNews screen sealed class LatestNewsUiState { data class Success(val news: List<ArticleHeadline>) : LatestNewsUiState() data class Error(val exception: Throwable) : LatestNewsUiState() }
Класс, отвечающий за обновление MutableStateFlow является производителем, а все классы, собирающие данные из StateFlow являются потребителями. В отличие от «холодного» потока, созданного с помощью построителя flow , StateFlow является «горячим» : сбор данных из потока не запускает код производителя. StateFlow всегда активен и находится в памяти, и становится доступным для сборки мусора только тогда, когда на него нет других ссылок из корневого каталога сборки мусора.
Когда новый потребитель начинает сбор данных из потока, он получает последнее состояние в потоке и все последующие состояния. Такое поведение можно найти и в других классах наблюдаемых объектов, например, LiveData .
View отслеживает StateFlow так же, как и любые другие потоки:
class LatestNewsActivity : ComponentActivity() { private val latestNewsViewModel: LatestNewsViewModel = TODO() // getViewModel() override fun onCreate(savedInstanceState: Bundle?) { // ... // Start a coroutine in the lifecycle scope lifecycleScope.launch { // repeatOnLifecycle launches the block in a new coroutine every time the // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED. repeatOnLifecycle(Lifecycle.State.STARTED) { // Trigger the flow and start listening for values. // Note that this happens when lifecycle is STARTED and stops // collecting when the lifecycle is STOPPED latestNewsViewModel.uiState.collect { uiState -> // New value received when (uiState) { is LatestNewsUiState.Success -> showFavoriteNews(uiState.news) is LatestNewsUiState.Error -> showError(uiState.exception) } } } } } }
Для преобразования любого потока в StateFlow используйте промежуточный оператор stateIn .
StateFlow, Flow, and LiveData
StateFlow и LiveData имеют сходства. Оба являются наблюдаемыми классами-хранилищами данных и оба следуют схожей схеме при использовании в архитектуре приложения.
Однако следует отметить, что StateFlow и LiveData ведут себя по-разному:
-
StateFlowтребует передачи начального состояния в конструктор, в то время какLiveDataэтого не требует. -
LiveData.observe()автоматически отменяет регистрацию потребителя, когда представление переходит в состояниеSTOPPED, в то время как сбор данных изStateFlowили любого другого потока не прекращается автоматически. Для достижения аналогичного поведения необходимо собирать данные из потокаLifecycle.repeatOnLifecycle.
Making cold flows hot using shareIn
StateFlow — это « горячий» поток: он остается в памяти до тех пор, пока выполняется сборка мусора или пока существуют какие-либо другие ссылки на него из корневого каталога сборки мусора. «Горячие» потоки можно сделать «холодными», используя оператор shareIn .
В качестве примера рассмотрим callbackFlow , созданный в Kotlin Flows . Вместо того чтобы каждый коллектор создавал новый поток, вы можете обмениваться данными, полученными из Firestore, между коллекторами, используя shareIn . Вам необходимо передать следующие данные:
-
CoroutineScopeэто область видимости, используемая для совместного использования потока. Эта область видимости должна существовать дольше, чем любой потребитель, чтобы поддерживать общий поток в активном состоянии столько, сколько необходимо. - Количество предметов, которые необходимо повторно предоставить каждому новому коллекционеру.
- The start behavior policy.
class NewsRemoteDataSource( private val externalScope: CoroutineScope ) { val latestNews: Flow<List<ArticleHeadline>> = flow { // ... }.shareIn( externalScope, replay = 1, started = SharingStarted.WhileSubscribed() ) }
В этом примере поток latestNews воспроизводит последний отправленный элемент новому сборщику и остается активным до тех пор, пока externalScope активен и есть активные сборщики. Политика запуска SharingStarted.WhileSubscribed() поддерживает активность вышестоящего производителя, пока есть активные подписчики. Доступны и другие политики запуска, например, SharingStarted.Eagerly для немедленного запуска производителя или SharingStarted.Lazily для начала обмена данными после появления первого подписчика и поддержания активности потока навсегда.
SharedFlow
Функция shareIn возвращает SharedFlow — поток данных, который отправляет значения всем потребителям, получающим от него данные. SharedFlow — это гибко настраиваемая обобщенная версия StateFlow .
Вы можете создать SharedFlow без использования shareIn . Например, вы можете использовать SharedFlow для отправки уведомлений остальной части приложения, чтобы весь контент периодически обновлялся одновременно. Помимо получения последних новостей, вы также можете захотеть обновить раздел информации о пользователе с его коллекцией любимых тем. В следующем фрагменте кода TickHandler предоставляет доступ SharedFlow , чтобы другие классы знали, когда нужно обновить его контент. Как и в случае с StateFlow , используйте свойство типа MutableSharedFlow в классе для отправки элементов в поток:
// Class that centralizes when the content of the app needs to be refreshed class TickHandler( private val externalScope: CoroutineScope, private val tickIntervalMs: Long = 5000 ) { // Backing property to avoid flow emissions from other classes private val _tickFlow = MutableSharedFlow<Unit>(replay = 0) val tickFlow: SharedFlow<Unit> = _tickFlow init { externalScope.launch { while (true) { _tickFlow.emit(Unit) delay(tickIntervalMs) } } } }
class NewsRepository( // ... private val tickHandler: TickHandler, private val externalScope: CoroutineScope ) { init { externalScope.launch { // Listen for tick updates tickHandler.tickFlow.collect { refreshLatestNews() } } } suspend fun refreshLatestNews() { /* ... */ } // ... }
Вы можете настроить поведение SharedFlow следующими способами:
-
replayпозволяет повторно отправить ряд ранее отправленных значений новым подписчикам. -
onBufferOverflowпозволяет задать политику обработки событий при заполнении буфера элементами, подлежащими отправке. Значение по умолчанию —BufferOverflow.SUSPEND, которое приостанавливает выполнение программы. Другие варианты —DROP_LATESTилиDROP_OLDEST.
MutableSharedFlow также имеет свойство subscriptionCount , содержащее количество активных сборщиков данных, что позволяет оптимизировать бизнес-логику. Кроме того, MutableSharedFlow содержит функцию resetReplayCache если вы не хотите повторно воспроизводить последнюю информацию, отправленную в поток.
Additional flow resources
- Kotlin flows on Android
- Testing Kotlin flows on Android
- Что нужно знать об операторах shareIn и stateIn в Flow
- Migrating from LiveData to Kotlin Flow
- Дополнительные ресурсы по сопрограммам и алгоритмам потока выполнения в Kotlin.