StateFlow
и SharedFlow
— это API-интерфейсы Flow , которые позволяют потокам оптимально отправлять обновления состояния и передавать значения нескольким потребителям.
StateFlow
StateFlow
— это наблюдаемый поток владельца состояния, который отправляет текущие и новые обновления состояния своим сборщикам. Текущее значение состояния также можно прочитать через его свойство value
. Чтобы обновить состояние и отправить его в поток, присвойте новое значение свойству value
класса MutableStateFlow
.
В Android StateFlow
отлично подходит для классов, которым необходимо поддерживать наблюдаемое изменяемое состояние.
Следуя примерам из потоков Kotlin , 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 View 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 : AppCompatActivity() {
private val latestNewsViewModel = // 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 и LiveData
StateFlow
и LiveData
имеют сходство. Оба являются наблюдаемыми классами-держателями данных и оба следуют одному и тому же шаблону при использовании в архитектуре вашего приложения.
Однако обратите внимание, что StateFlow
и LiveData
ведут себя по-разному:
-
StateFlow
требует, чтобы исходное состояние было передано конструктору, аLiveData
этого не делает. -
LiveData.observe()
автоматически отменяет регистрацию потребителя, когда представление переходит в состояниеSTOPPED
, тогда как сбор изStateFlow
или любого другого потока не прекращает сбор автоматически. Чтобы добиться такого же поведения, вам необходимо собрать поток из блокаLifecycle.repeatOnLifecycle
.
Делаем холодные потоки горячими с помощью shareIn
StateFlow
— это горячий поток — он остается в памяти до тех пор, пока поток собирается или пока существуют другие ссылки на него из корня сборки мусора. Вы можете превратить холодные потоки в горячие, используя оператор shareIn
.
Используя в качестве примера callbackFlow
, созданный в потоках Kotlin , вместо того, чтобы каждый сборщик создавал новый поток, вы можете поделиться данными, полученными из Firestore, между сборщиками с помощью shareIn
. Вам необходимо передать следующее:
-
CoroutineScope
, используемый для совместного использования потока. Эта область должна существовать дольше, чем любой потребитель, чтобы общий поток оставался активным столько, сколько необходимо. - Количество предметов, которые будут воспроизведены каждому новому коллекционеру.
- Политика стартового поведения.
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
для начала общего доступа после появления первого подписчика и сохранения активности потока навсегда.
Общий поток
Функция 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<Event<String>> = _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
если вы не хотите воспроизводить последнюю информацию, отправленную в поток.
Дополнительные ресурсы потока
- Kotlin работает на Android
- Тестирование потоков Kotlin на Android
- Что нужно знать об операторах ShareIn и StateIn Flow
- Миграция с LiveData на Kotlin Flow
- Дополнительные ресурсы для сопрограмм и потоков Kotlin