StateFlow
e SharedFlow
são APIs Flow.
Elas permitem que os fluxos emitam atualizações de estado de maneira otimizada e que também emitam valores para vários
consumidores.
StateFlow
StateFlow
é um fluxo observável detentor de estado que emite o estado atual e o novo
atualizações para os coletores. O valor do estado atual também pode ser lido na
value
. Para atualizar o estado e enviá-lo ao fluxo, atribua um novo valor à
propriedade value
da classe
MutableStateFlow
(link em inglês).
No Android, StateFlow
é uma ótima opção para classes que precisam manter
um estado mutável observável.
Seguindo os exemplos de fluxos Kotlin, um StateFlow
pode ser exposto do LatestNewsViewModel
para que View
possa
detectar as atualizações de estado da interface e, inerentemente, fazer com que o estado da tela sobreviva
às mudanças de configuração.
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()
}
A classe responsável por atualizar uma MutableStateFlow
é o produtor,
e todas as classes coletando do StateFlow
são os consumidores. Ao contrário
de um fluxo frio criado usando o builder flow
, um StateFlow
é quente:
a coleta de dados do fluxo não aciona nenhum código de produtor. Um StateFlow
sempre fica ativo e na memória. Ele se torna qualificado para coleta
de lixo somente quando não há outras referências a ele em uma raiz da coleta
de lixo.
Quando um novo consumidor começa a coletar do fluxo, ele recebe o último
estado no stream e os estados subsequentes. Outras classes observáveis, como
LiveData
,
também apresentam esse comportamento.
View
detecta StateFlow
da mesma forma que faria com qualquer outro fluxo:
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)
}
}
}
}
}
}
Para converter qualquer fluxo em um StateFlow
, use o
stateIn
operador intermediário.
StateFlow, Flow e LiveData
StateFlow
e LiveData
têm
semelhanças. Ambas são classes observáveis do detentor de dados e seguem
um padrão semelhante quando usadas na arquitetura do app.
No entanto, observe que StateFlow
e
LiveData
se comportam de maneira diferente:
StateFlow
necessita que um estado inicial seja transmitido para o construtor, enquantoLiveData
não.LiveData.observe()
cancela o registro do consumidor automaticamente quando a visualização vai para o estadoSTOPPED
, enquanto a coleta de umStateFlow
ou qualquer outro fluxo não para a coleta automaticamente. Para conseguir o mesmo comportamento, você precisa coletar o fluxo de um objetoLifecycle.repeatOnLifecycle
bloco de recursos dependente.
Como converter fluxos frios em quentes com shareIn
StateFlow
é um fluxo quente. Ele permanece na memória enquanto o fluxo é
coletado ou enquanto qualquer outra referência a ele existe em uma raiz
de coleta de lixo. É possível transformar fluxos frios em quentes usando o
shareIn
usando um operador lógico.
Usando o callbackFlow
criado em fluxos Kotlin como
exemplo, em vez de fazer com que cada coletor crie um novo fluxo, é possível compartilhar
os dados recuperados do Firestore entre coletores usando shareIn
.
As seguintes informações precisam ser transmitidas:
- Um
CoroutineScope
que é usado para compartilhar o fluxo. O escopo precisa sobreviver mais que qualquer consumidor para manter o fluxo compartilhado ativo pelo tempo necessário. - O número de itens a serem repetidos para cada novo coletor.
- A política de comportamento inicial.
class NewsRemoteDataSource(...,
private val externalScope: CoroutineScope,
) {
val latestNews: Flow<List<ArticleHeadline>> = flow {
...
}.shareIn(
externalScope,
replay = 1,
started = SharingStarted.WhileSubscribed()
)
}
Neste exemplo, o fluxo latestNews
reproduz novamente o último item emitido
para um novo coletor e permanece ativo enquanto externalScope
e coletores também estão. A política de início SharingStarted.WhileSubscribed()
mantém o produtor upstream ativo enquanto há inscritos
ativos. Outras políticas de início estão disponíveis, como
SharingStarted.Eagerly
para iniciar o produtor imediatamente ou
SharingStarted.Lazily
para começar a compartilhar após o primeiro inscrito ser exibido
e manter o fluxo ativo para sempre.
SharedFlow
A função shareIn
retorna um SharedFlow
, um fluxo quente que emite valores
para todos os consumidores que coletam dados dele. Um SharedFlow
é uma
generalização altamente configurável de StateFlow
.
Você pode criar um SharedFlow
sem usar shareIn
. Por exemplo, é
possível usar um SharedFlow
para enviar marcações ao restante do app para que
todo o conteúdo seja atualizado periodicamente ao mesmo tempo. Além de
buscar as notícias mais recentes, você também pode atualizar a seção
de informações do usuário com a coleção de temas favoritos dele. No snippet de código
a seguir, um TickHandler
expõe um SharedFlow
para que outras
classes saibam quando atualizar o conteúdo. Como acontece com o StateFlow
, use uma
propriedade de backup do tipo MutableSharedFlow
em uma classe para enviar itens
para o fluxo:
// 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() { ... }
...
}
É possível personalizar o comportamento de SharedFlow
das seguintes maneiras:
replay
permite reenviar diversos valores previamente emitidos para novos inscritos.onBufferOverflow
permite especificar uma política para quando o buffer estiver cheio de itens a serem enviados. O valor padrão éBufferOverflow.SUSPEND
, o que suspende o autor da chamada. Outras opções sãoDROP_LATEST
ouDROP_OLDEST
.
MutableSharedFlow
também tem uma propriedade subscriptionCount
que contém
o número de coletores ativos para que você possa otimizar sua lógica
de negócios de acordo com essa informação. MutableSharedFlow
também contém uma função resetReplayCache
se você não quiser repetir as informações mais recentes enviadas ao fluxo.
Recursos de fluxo adicionais
- Fluxos do Kotlin no Android
- Como testar fluxos do Kotlin no Android
- Informações importantes sobre os operadores shareIn e stateIn do Flow (link em inglês)
- Migrar do LiveData para fluxos do Kotlin (link em inglês)
- Outros recursos para corrotinas e fluxos do Kotlin