Kotlin Flow vs StateFlow vs SharedFlow: perguntas de entrevista Android em 2026

As perguntas sobre Kotlin Flow vs StateFlow vs SharedFlow que os entrevistadores de Android fazem em 2026, com respostas claras, tabela comparativa e código pronto para produção.

Comparação entre Kotlin Flow, StateFlow e SharedFlow para entrevistas de Android

Kotlin Flow vs StateFlow vs SharedFlow é um dos temas mais recorrentes em entrevistas de Android em 2026, e confundir os três é a maneira mais rápida de perder pontos na rodada de coroutines. StateFlow e SharedFlow são ambos fluxos hot construídos sobre o Flow, mas existem para resolver problemas diferentes: manter estado versus difundir eventos. As perguntas a seguir são as que os entrevistadores de Android realmente fazem, com respostas precisas e código pronto para produção.

A resposta em 20 segundos

Um Flow é cold e executa seu produtor uma vez para cada coletor. StateFlow é um fluxo hot e conflated que sempre mantém um único valor atual, ideal para estado de UI. SharedFlow é um fluxo hot sem valor inicial obrigatório, ideal para eventos únicos como navegação ou um snackbar.

Kotlin Flow vs StateFlow vs SharedFlow: as diferenças fundamentais

StateFlow e SharedFlow são especializações de SharedFlow, que por sua vez é um Flow hot. A distinção que os entrevistadores investigam é cold versus hot, se um valor atual é retido e como cada um se comporta com emissões duplicadas. Um modelo mental conciso: Flow é uma receita, StateFlow é um único valor mutável e SharedFlow é um barramento de eventos.

| Propriedade | Flow (cold) | StateFlow | SharedFlow | |---|---|---|---| | Temperatura | Cold | Hot | Hot | | Valor inicial | Nenhum | Obrigatório | Opcional (via replay) | | Mantém valor atual | Não | Sim, via .value | Não | | Emite duplicatas | Sim | Não (conflated + deduplicado) | Configurável | | Coletores adicionais | Novo fluxo a cada vez | Compartilhado | Compartilhado | | Melhor para | Pipelines de dados assíncronos | Estado de UI | Eventos únicos |

A documentação oficial de coroutines do Kotlin trata esse comportamento cold-by-default como a propriedade que define o Flow, então uma boa resposta começa por aí.

Por que um Kotlin Flow é cold por padrão?

Um fluxo cold não faz nada até que collect() seja chamado, e reexecuta seu bloco produtor para cada coletor. Dois coletores no mesmo fluxo cold disparam duas chamadas de rede independentes. Esse é o conceito mais cobrado de todos, então um exemplo curto ancora a resposta.

SearchRepository.ktkotlin
fun searchResults(query: String): Flow<List<Result>> = flow {
    // This block runs fresh for every collector.
    // Nothing executes until a collector calls collect().
    val results = api.search(query) // suspending network call
    emit(results)                   // pushed downstream to the collector
}

Como o produtor reinicia a cada coletor, fluxos cold são o padrão certo para pipelines de dados que devem rodar sob demanda. Eles só se tornam um problema quando um valor precisa ser compartilhado por uma tela inteira, que é exatamente onde StateFlow e SharedFlow entram.

Perguntas de entrevista sobre StateFlow: estado que sempre emite

StateFlow é um fluxo hot que sempre tem um valor e reproduz esse valor mais recente para cada novo coletor. Ele exige um valor inicial, expõe .value para leituras síncronas e faz conflation das emissões: atualizações rápidas e sucessivas podem pular valores intermediários, e definir o mesmo valor duas vezes não emite nada porque ele deduplica por igualdade. Isso o torna o substituto moderno do LiveData dentro de um ViewModel.

SearchViewModel.ktkotlin
class SearchViewModel(private val repo: SearchRepository) : ViewModel() {
    // MutableStateFlow demands an initial value, so the UI always has something to render.
    private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Idle)
    val uiState: StateFlow<SearchUiState> = _uiState.asStateFlow()

    fun onQueryChanged(query: String) {
        _uiState.value = SearchUiState.Loading
        viewModelScope.launch {
            val results = repo.search(query)
            // update {} applies the change atomically, safe under concurrent callers.
            _uiState.update { SearchUiState.Success(results) }
        }
    }
}

Um follow-up comum: por que expor asStateFlow() em vez do campo mutável? Isso entrega à UI uma visão somente leitura, de modo que o estado só pode mudar através do ViewModel, o que mantém o fluxo de dados unidirecional intacto. O próprio guia de StateFlow e SharedFlow do Android recomenda exatamente esse padrão.

SharedFlow vs StateFlow: escolhendo o tipo certo para eventos

A pergunta-armadilha clássica é: "o StateFlow consegue entregar eventos únicos?" A resposta honesta é não, não de forma segura. Como o StateFlow faz conflation e deduplicação, um evento de navegação pode ser descartado sob atualizações rápidas, e ele dispara de novo em uma mudança de configuração porque um novo coletor reproduz o valor retido. SharedFlow com replay = 0 resolve os dois casos: nada é retido, e cada emissão alcança apenas os coletores ativos no momento da emissão.

CheckoutViewModel.ktkotlin
class CheckoutViewModel : ViewModel() {
    // replay = 0: a late collector must NOT re-receive a past navigation event.
    private val _events = MutableSharedFlow<CheckoutEvent>(replay = 0)
    val events: SharedFlow<CheckoutEvent> = _events.asSharedFlow()

    fun onPaymentConfirmed() {
        viewModelScope.launch {
            // emit() suspends if the buffer is full; tryEmit() is the non-suspending variant.
            _events.emit(CheckoutEvent.NavigateToReceipt)
        }
    }
}

Para perguntas sobre arquitetura, o raciocínio mais profundo por trás de separar estado de eventos aparece na comparação entre MVVM e MVI, em que o MVI trata eventos como um stream explícito em vez de estado mutável.

Convertendo um Flow cold em um StateFlow hot com stateIn

Entrevistadores frequentemente perguntam como transformar o fluxo cold de um repositório em estado de UI. A resposta é stateIn (para um único valor retido) ou shareIn (para uma difusão sem valor atual). O parâmetro SharingStarted controla quando o upstream fica ativo, e WhileSubscribed(5_000) é a escolha padrão porque mantém o fluxo vivo durante uma rotação sem vazá-lo quando a tela é fechada.

ProfileViewModel.ktkotlin
val profile: StateFlow<Profile> = repo.profileStream()
    .stateIn(
        scope = viewModelScope,
        // Keep the upstream alive 5s after the last collector leaves, surviving rotation.
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = Profile.EMPTY
    )

A diferença entre stateIn e shareIn é exatamente a diferença entre StateFlow e SharedFlow: stateIn precisa de um initialValue e mantém estado, enquanto shareIn recebe uma contagem de replay e difunde. Revisar o modelo mais amplo de coroutines neste guia para dominar as coroutines do Kotlin ajuda a conectar esses operadores a escopos e cancelamento.

Pronto para mandar bem nas entrevistas de Android?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Coletando fluxos com segurança no ciclo de vida do Android

Uma pergunta de nível sênior é como coletar um fluxo sem vazar trabalho enquanto a tela está em segundo plano. Coletar dentro de um launch comum continua rodando quando a UI está parada, desperdiçando CPU e arriscando crashes. repeatOnLifecycle(STARTED) cancela a coleta no STOP e a reinicia no START.

ProfileFragment.kt (View system)kotlin
viewLifecycleOwner.lifecycleScope.launch {
    // Collection restarts on STARTED and cancels on STOPPED: no wasted work in the background.
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { state -> render(state) }
    }
}

No Jetpack Compose o equivalente é collectAsStateWithLifecycle(), que para de coletar quando o app vai para segundo plano e retoma ao voltar.

ProfileScreen.kt (Jetpack Compose)kotlin
@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {
    // collectAsStateWithLifecycle stops collecting when the app is backgrounded.
    val state by viewModel.uiState.collectAsStateWithLifecycle()
    ProfileContent(state)
}

Essa consciência do ciclo de vida é o motivo pelo qual o StateFlow combinado com collectAsStateWithLifecycle é o padrão de estado predominante em apps Compose modernos, um ponto aprofundado nestas perguntas de entrevista sobre Jetpack Compose.

Como testar emissões de StateFlow e SharedFlow

Testes são onde os candidatos sêniores se destacam. A abordagem ingênua lê stateFlow.value uma única vez, mas isso perde estados intermediários e não consegue observar um SharedFlow de forma alguma. A resposta padrão cita a biblioteca Turbine, que suspende até cada emissão chegar e falha o teste se um valor esperado nunca vier.

SearchViewModelTest.ktkotlin
@Test
fun `emits Loading then Success on query`() = runTest {
    val viewModel = SearchViewModel(fakeRepo)

    viewModel.uiState.test {
        assertEquals(SearchUiState.Idle, awaitItem())     // initial value
        viewModel.onQueryChanged("kotlin")
        assertEquals(SearchUiState.Loading, awaitItem())  // intermediate state
        assertEquals(SearchUiState.Success(fakeResults), awaitItem())
        cancelAndIgnoreRemainingEvents()
    }
}

Combinar o Turbine com runTest e um test dispatcher mantém as asserções determinísticas, e mencionar StandardTestDispatcher versus UnconfinedTestDispatcher demonstra experiência real em testes de coroutines.

Follow-ups comuns em entrevistas sobre Kotlin Flow

Entrevistadores encerram com perguntas rápidas. O que SharingStarted.WhileSubscribed(5_000) faz? Ele inicia o upstream no primeiro inscrito e o para 5 segundos depois que o último cancela a inscrição, tempo suficiente para sobreviver a uma rotação. Por que o StateFlow ignora valores duplicados? Ele compara com equals(), então emitir um valor igual é um no-op, e é por isso que data classes importam para o estado. O SharedFlow pode se comportar como StateFlow? Definir replay = 1 faz com que ele retenha o último valor, mas ele ainda não tem um .value síncrono e nunca deduplica. O que controla o back-pressure em um SharedFlow? Os parâmetros extraBufferCapacity e onBufferOverflow, sendo BufferOverflow.DROP_OLDEST uma escolha comum para eventos. Prática mais aprofundada nesses operadores está no módulo de entrevista sobre coroutines e Flow do Kotlin.

O erro que reprova candidatos

Usar StateFlow para eventos de navegação ou snackbar. Como ele reproduz seu último valor para novos coletores, o evento dispara de novo após uma rotação e o usuário é navegado duas vezes. Recorra ao SharedFlow com replay = 0, ou modele um evento único que é limpo após ser tratado.

Vale a pena dar uma olhada no código-fonte do kotlinx.coroutines e na sua referência do pacote flow no GitHub antes de uma entrevista, já que o KDoc de StateFlow e SharedFlow descreve as garantias de conflation e replay com precisão.

Conclusão

A distinção entre Kotlin Flow vs StateFlow vs SharedFlow recompensa uma linguagem precisa em uma entrevista. Pontos-chave para levar:

  • Flow é cold: o produtor roda novamente para cada coletor, então serve para pipelines de dados sob demanda.
  • StateFlow é hot, sempre mantém um valor e faz conflation e deduplicação, o que o torna a ferramenta para estado de UI.
  • SharedFlow é hot e não exige valor inicial, o que o torna a escolha correta para eventos únicos.
  • Converta cold em hot com stateIn ou shareIn, e use SharingStarted.WhileSubscribed(5_000) para sobreviver à rotação.
  • Colete com repeatOnLifecycle(STARTED) ou collectAsStateWithLifecycle() para evitar trabalho em segundo plano e vazamentos.
  • Nunca entregue eventos através do StateFlow: seu comportamento de replay os dispara de novo após uma mudança de configuração.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

#android
#kotlin
#coroutines
#flow
#interview

Compartilhar

Artigos relacionados