SharedFlow: o que é, SharedFlow vs StateFlow no Android

Autor: IT Sectr Publicado: 2026-02-20 Tempo de leitura: 9 min

SharedFlow é um fluxo reativo quente da biblioteca Kotlin Coroutines, otimizado para eventos únicos (one-shot events) que não devem se repetir ao girar a tela ou recriar o assinante. Mostramos como o SharedFlow difere do StateFlow: ao contrário do StateFlow, o SharedFlow não armazena o último valor para novos assinantes e suporta configuração de replay, extraBufferCapacity e onBufferOverflow. De acordo com o Google (Android Developers, 2025), o SharedFlow é a solução recomendada para comandos de navegação, mensagens Snackbar e outros eventos que devem ser processados exatamente uma vez.

Pontos Principais

  • SharedFlow — fluxo quente para eventos únicos: novos assinantes não recebem valores anteriores sem configuração de replay.
  • MutableSharedFlow — versão mutável com métodos emit() e tryEmit() para envio de eventos.
  • SharedFlow vs StateFlow: SharedFlow não conflaciona valores (pode armazenar vários em buffer), não requer valor inicial, adequado para eventos únicos.
  • replay — número de eventos recentes reproduzidos para novos assinantes (padrão 0).
  • extraBufferCapacity — buffer adicional para eventos além do replay, evitando o bloqueio de emit().

O que é SharedFlow em Kotlin?

SharedFlow é um fluxo quente (hot flow) da biblioteca kotlinx.coroutines.flow que, ao contrário do StateFlow, não está vinculado a um único estado e pode emitir um número arbitrário de eventos para assinantes arbitrários. SharedFlow é o tipo base para StateFlow — StateFlow é implementado através de SharedFlow com replay = 1.

A principal característica do SharedFlow é que ele não precisa armazenar o último valor. Por padrão (replay = 0), um novo assinante não recebe nada até que um novo evento seja enviado. Isso torna o SharedFlow ideal para cenários onde um evento deve ser processado exatamente uma vez: navegação, Snackbar, notificações do sistema, resultados de digitalização de QR code.

O SharedFlow foi estabilizado no kotlinx.coroutines 1.4.0 (novembro de 2020) junto com o StateFlow. De acordo com a documentação do Kotlin Coroutines (2025), o SharedFlow usa bloqueio de granulação fina para sincronização de assinantes e fornece escalabilidade linear para mais de 1000 assinantes simultâneos sem degradação de desempenho, confirmado por testes da JetBrains.

SharedFlow vs StateFlow: quando usar cada um

A escolha entre SharedFlow e StateFlow depende da semântica dos dados transferidos: estado (StateFlow) ou evento (SharedFlow). Abaixo estão critérios claros com exemplos.

CritérioSharedFlowStateFlow
SemânticaEventos únicos (navegação, toast, alerta)Estado da UI (lista, carregamento, erro)
Valor inicialNão obrigatórioObrigatório
Reprodução na assinaturaApenas se replay > 0Sempre o último valor
ConflaçãoNão — eventos não são perdidos (se o buffer não estiver cheio)Sim — armazena apenas o último
BufferizaçãoConfigurável via replay + extraBufferCapacityApenas 1 (replay=1 fixo)
UsonavigationEvent, showSnackbar, openDialogitems, isLoading, uiState

A regra mais simples: se os dados devem ser mostrados ao girar a tela — é estado (StateFlow). Se ao girar a tela o evento não deve se repetir — é um evento único (SharedFlow). Por exemplo, um toast com mensagem de erro é SharedFlow: ao girar, o toast não deve aparecer novamente. Uma lista de produtos é StateFlow: ao girar, a lista deve permanecer na tela.

Na IT Sectr usamos SharedFlow para: comandos de navegação (transição de tela, abertura de deep link), eventos de UI (Snackbar, AlertDialog), notificações do sistema (atualizações de dados em segundo plano, resultado de pagamento), eventos de análise (registro, rastreamento).

MutableSharedFlow: emit, tryEmit e bufferização

MutableSharedFlow é a versão mutável do SharedFlow com métodos emit() (suspend) e tryEmit() (não suspend) para envio de eventos. emit() suspende se o buffer estiver cheio e onBufferOverflow = SUSPEND. tryEmit() retorna um Boolean indicando se o evento foi adicionado com sucesso ao buffer.

kotlin
class EventBus {
    private val _events = MutableSharedFlow<UiEvent>(
        replay = 0,
        extraBufferCapacity = 10,
        onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    val events: SharedFlow<UiEvent> get() = _events

    suspend fun sendEvent(event: UiEvent) {
        _events.emit(event)
    }

    fun trySendEvent(event: UiEvent): Boolean {
        return _events.tryEmit(event)
    }
}

sealed interface UiEvent {
    data class ShowSnackbar(val message: String) : UiEvent
    data class NavigateTo(val route: String) : UiEvent
    data class ShowDialog(val title: String, val message: String) : UiEvent
}

Os parâmetros do construtor são criticamente importantes: replay = 0 garante que o evento não se repita para um novo assinante; extraBufferCapacity = 10 fornece um buffer para emissão rápida de eventos antes que a UI assine; DROP_OLDEST é a estratégia de estouro: eventos antigos são descartados, novos são preservados. De acordo com Kotlin Coroutines Performance (JetBrains, 2024), SharedFlow com extraBufferCapacity = 64 processa mais de 100.000 eventos por segundo sem perdas.

SharedFlow para eventos únicos: o padrão Event

Padrão Event (ou UiEvent) é a forma recomendada pelo Google para passar eventos únicos de ViewModel para View. Ao contrário do estado (StateFlow), um evento deve ser processado exatamente uma vez e, ao girar a tela, não deve se repetir. SharedFlow com replay = 0 é ideal para esta tarefa.

kotlin
class CheckoutViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<CheckoutState>(CheckoutState.Idle)
    val uiState: StateFlow<CheckoutState> get() = _uiState

    private val _event = MutableSharedFlow<CheckoutEvent>()
    val event: SharedFlow<CheckoutEvent> get() = _event

    fun placeOrder() {
        viewModelScope.launch {
            _uiState.value = CheckoutState.Loading
            try {
                val orderId = orderRepository.createOrder(cart)
                _uiState.value = CheckoutState.Success(orderId)
                _event.emit(CheckoutEvent.NavigateToOrderTracking(orderId))
            } catch (e: Exception) {
                _uiState.value = CheckoutState.Error(e.message)
                _event.emit(CheckoutEvent.ShowErrorSnackbar(e.message ?: "Erro de layout"))
            }
        }
    }
}

sealed interface CheckoutEvent {
    data class NavigateToOrderTracking(val orderId: String) : CheckoutEvent
    data class ShowErrorSnackbar(val message: String) : CheckoutEvent
}

Na View (Activity/Fragment): a assinatura de eventos deve ser feita em lifecycleScope com repeatOnLifecycle(STATE.STARTED). Em cada entrada em STARTED, a assinatura é recriada, mas o evento não se repete porque o SharedFlow com replay=0 já o liberou. Isso garante que a navegação para a tela de rastreamento de pedidos ocorra apenas uma vez, não a cada rotação.

Parâmetros do SharedFlow: replay, extraBufferCapacity, onBufferOverflow

O construtor do MutableSharedFlow aceita três parâmetros que determinam o comportamento do buffer. Configuração incorreta pode levar à perda de eventos ou bloqueio de emit().

ParâmetroTipoPadrãoDescrição
replayInt0Número de eventos recentes reproduzidos para um novo assinante. 0 = não reproduzir, 1 = como StateFlow
extraBufferCapacityInt0Buffer adicional além do replay. Eventos são armazenados em um buffer circular. 64 é o limite recomendado para a maioria dos cenários
onBufferOverflowBufferOverflowSUSPENDEstratégia quando o buffer está cheio: SUSPEND, DROP_OLDEST, DROP_LATEST
kotlin
// Configurações para diferentes cenários:

// 1. Eventos de UI únicos (navegação, toasts)
val uiEvents = MutableSharedFlow<UiEvent>(
    replay = 0,
    extraBufferCapacity = 5,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

// 2. Fluxo de replay para sincronização de estado (como StateFlow)
val stateLike = MutableSharedFlow<AppState>(
    replay = 1,
    extraBufferCapacity = 0
)

// 3. Emissão de eventos de alta frequência (análise, registros)
val analytics = MutableSharedFlow<AnalyticsEvent>(
    replay = 0,
    extraBufferCapacity = 100,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

Importante: extraBufferCapacity + replay = tamanho total do buffer. Se emit() for chamado mais rápido do que o assinante processa os eventos, o buffer enche e onBufferOverflow é acionado. Para eventos de UI, DROP_OLDEST é uma estratégia segura: eventos antigos (navegações não mais relevantes) são descartados em favor dos novos. Para transações financeiras, use SUSPEND — isso garante que nenhum evento seja perdido ao custo de bloquear o remetente.

Exemplos de código: SharedFlow em Kotlin

Exemplo 1: SharedFlow para navegação com Jetpack Navigation

Comandos de navegação são um caso de uso clássico para SharedFlow. Um Fragment assina os eventos e realiza a navegação. Ao girar a tela, o comando não se repete.

kotlin
// ViewModel
class AuthViewModel : ViewModel() {
    private val _navEvent = MutableSharedFlow<NavEvent>()
    val navEvent: SharedFlow<NavEvent> get() = _navEvent

    fun onLoginSuccess() {
        viewModelScope.launch {
            _navEvent.emit(NavEvent.NavigateTo(NavRoutes.HOME))
        }
    }

    fun onLogout() {
        viewModelScope.launch {
            _navEvent.emit(NavEvent.NavigateTo(NavRoutes.LOGIN))
        }
    }
}

sealed interface NavEvent {
    data class NavigateTo(val route: String) : NavEvent
    data class NavigateBack(val popUpTo: String? = null) : NavEvent
}

// No Fragment:
viewLifecycleOwner.lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.navEvent.collect { navEvent ->
            when (navEvent) {
                is NavEvent.NavigateTo -> findNavController().navigate(navEvent.route)
                is NavEvent.NavigateBack -> findNavController().popBackStack()
            }
        }
    }
}

Exemplo 2: SharedFlow com Room e operadores Flow

Um cenário complexo: SharedFlow para notificações de eventos em segundo plano combinado com StateFlow para a UI.

kotlin
class NotificationViewModel : ViewModel() {
    private val _toastMessage = MutableSharedFlow<String>()
    val toastMessage: SharedFlow<String> get() = _toastMessage

    private val _notifications = MutableStateFlow<List<Notification>>(emptyList())
    val notifications: StateFlow<List<Notification>> get() = _notifications

    init {
        viewModelScope.launch {
            notificationChannel
                .consumeAsFlow()
                .collect { notification ->
                    _notifications.value = _notifications.value + notification
                    _toastMessage.emit("Nova notificação: ${notification.title}")
                }
        }
    }

    fun dismissNotification(id: String) {
        _notifications.value = _notifications.value.filter { it.id != id }
    }

    fun markAllRead() {
        viewModelScope.launch {
            _notifications.value = _notifications.value.map { it.copy(isRead = true) }
            _toastMessage.emit("Todas as notificações marcadas como lidas")
        }
    }
}

Neste exemplo: StateFlow armazena a lista de notificações (estado — preservado na rotação), SharedFlow emite mensagens toast (eventos únicos — não repetidos na rotação). A combinação de dois tipos de Flow é o padrão recomendado pelo Google para ViewModel a partir de 2022.

Perguntas Frequentes

O SharedFlow pode perder um evento?

Sim, se o buffer estiver cheio e onBufferOverflow = DROP_OLDEST ou DROP_LATEST. SharedFlow não garante a entrega de cada evento — não é uma fila de mensagens (como Channel). Se precisar de entrega garantida de todos os eventos, use Channel com buffer ilimitado (UNLIMITED) ou BroadcastChannel (obsoleto). Para eventos de UI, a perda de eventos obsoletos (por exemplo, navegação antiga) é comportamento esperado, não um bug.

Como o SharedFlow difere do Channel?

Channel é uma fila FIFO onde cada evento é entregue a exatamente um assinante (ponto a ponto). SharedFlow é uma transmissão: cada evento é entregue a TODOS os assinantes ativos. SharedFlow é mais próximo de BroadcastChannel (que está obsoleto) e é adequado para cenários de um-para-muitos. Channel é para um-para-um (pools de threads, pipelines). De acordo com a recomendação da JetBrains, SharedFlow é o substituto do BroadcastChannel em todos os novos projetos.

Como tornar o SharedFlow seguro para threads?

SharedFlow já é seguro para threads — emit() e collect() estão devidamente sincronizados. Várias threads podem chamar emit() sem bloqueios, e todos os assinantes ativos recebem os eventos na ordem correta. tryEmit() não é bloqueante — retorna false se o buffer estiver cheio. Para sistemas de alta carga, use tryEmit() com DROP_OLDEST — isso evita o bloqueio de threads.

Por que o SharedFlow não é usado para estado?

SharedFlow sem replay=1 não armazena o último valor — ao girar a tela, um novo assinante não receberá o estado atual e a UI ficará vazia. Com replay=1, SharedFlow se comporta como StateFlow, mas perde a otimização de comparação via equals(), causando notificações desnecessárias quando o mesmo valor é emitido novamente. StateFlow é a escolha certa para estado; SharedFlow é para eventos.

Como testar o SharedFlow?

Para testar SharedFlow, use Turbine — uma biblioteca Kotlin para testar Flow. Turbine permite verificar cada emissão individualmente com timeouts e verificação de conclusão. Exemplo: viewModel.event.test { assertEquals(UiEvent.ShowSnackbar("OK"), awaitItem()) }. Você também pode usar .toList() em runTest especificando o número de eventos esperados.

Resumo

  • SharedFlow — um fluxo reativo quente para eventos únicos não vinculados ao último estado.
  • SharedFlow vs StateFlow: SharedFlow para eventos (navegação, toasts, alertas), StateFlow para estado (listas, carregamento, erros).
  • MutableSharedFlow com replay=0, extraBufferCapacity=5, DROP_OLDEST é a configuração padrão para eventos de UI.
  • emit() — função suspend para emissão bloqueante; tryEmit() — não suspend com resultado Boolean.
  • O padrão UiEvent com sealed class é a forma recomendada pelo Google para passar eventos únicos de ViewModel para View.
  • SharedFlow garante a entrega a cada assinante, mas não garante a entrega de cada evento quando o buffer transborda.
  • A combinação de SharedFlow + StateFlow em uma única ViewModel é o padrão ideal para uma arquitetura que separa estado e eventos.

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também