StateFlow
e SharedFlow
são APIs Flow
que permitem que os fluxos emitam atualizações de estado de maneira otimizada e valores para vários
consumidores.
StateFlow
StateFlow
(link em inglês) é um fluxo observável com estado que emite as atualizações de estado
novas e atuais para os coletores. O valor do estado atual também pode ser lido usando a
propriedade
value
(link em inglês) dele. 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 IU 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(news: List<ArticleHeadline>): LatestNewsUiState()
data class Error(exception: Throwable): LatestNewsUiState()
}
A classe responsável por atualizar um MutableStateFlow
é o produtor,
e todas as classes coletadas 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. É possível ver esse comportamento
em outras classes observáveis, como
LiveData
.
View
detecta StateFlow
da mesma forma que faria com qualquer outro fluxo:
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
// This coroutine will run the given block when the lifecycle
// is at least in the Started state and will suspend when
// the view moves to the Stopped state
lifecycleScope.launchWhenStarted {
// Triggers the flow and starts listening for values
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
operador intermediário
stateIn
(link em inglês).
StateFlow, Flow e LiveData
StateFlow
e LiveData
têm
semelhanças. Ambos são classes observáveis do armazenador de dados e seguem
um padrão semelhante quando usados 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 faz isso.
No exemplo anterior, que usou launchWhenStarted
para coletar o fluxo,
quando a corrotina que aciona a coleta de fluxos é suspensa à medida que
View
vai para o segundo plano, os produtores subjacentes permanecem ativos.
Com implementações quentes, tenha cuidado ao coletar quando a IU não estiver na tela, porque isso pode desperdiçar recursos. Em vez disso, é possível interromper manualmente a coleta do fluxo, conforme mostrado no exemplo a seguir:
class LatestNewsActivity : AppCompatActivity() {
...
// Coroutine listening for UI states
private var uiStateJob: Job? = null
override fun onStart() {
super.onStart()
// Start collecting when the View is visible
uiStateJob = lifecycleScope.launch {
latestNewsViewModel.uiState.collect { uiState -> ... }
}
}
override fun onStop() {
// Stop collecting when the View goes to the background
uiStateJob?.cancel()
super.onStop()
}
}
Outra maneira de parar de detectar mudanças em uiState
quando a
visualização não está visível é converter o fluxo para LiveData
usando a função
asLiveData()
da biblioteca lifecycle-livedata-ktx
:
class LatestNewsActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
latestNewsViewModel.uiState.asLiveData().observe(owner = this) { state ->
// Handle UI state
}
}
}
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. Use o operador
shareIn
(link em inglês) para converter os fluxos frios em quentes.
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
está
ativo e há coletores ativos. 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 dela. 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 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 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.