O que é Flow em Kotlin?

O Flow é o mecanismo do Kotlin para trabalhar com streams de dados assíncronos. Enquanto uma função suspend retorna um único valor, um Flow pode emitir múltiplos valores ao longo do tempo, um de cada vez.

Pense no Flow como uma torneira: os dados vão saindo conforme são produzidos, e você consome na hora que quiser.

Sintaxe básica

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun contagem(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(500)
        emit(i)
    }
}

fun main() = runBlocking {
    contagem().collect { valor ->
        println("Recebido: $valor")
    }
}

O flow { } cria o produtor, o emit() envia cada valor e o collect consome. Simples assim.

Flow é “cold”

Uma característica importante: o Flow é cold, ou seja, ele só começa a produzir dados quando alguém chama collect. Se ninguém coletar, nada acontece.

val meuFlow = flow {
    println("Flow iniciado!") // Só executa quando alguém coletar
    emit(1)
    emit(2)
}

Operadores úteis

Flow vem com vários operadores pra transformar e filtrar dados:

fun main() = runBlocking {
    (1..10).asFlow()
        .filter { it % 2 == 0 }
        .map { it * it }
        .collect { println(it) }
    // 4, 16, 36, 64, 100
}

Alguns operadores mais usados:

  • map — transforma cada valor
  • filter — filtra valores
  • take — pega os N primeiros
  • onEach — executa ação em cada valor
  • catch — trata erros no fluxo

Exemplo prático: atualizações em tempo real

fun atualizacoesDePreco(): Flow<Double> = flow {
    val precos = listOf(45.50, 46.20, 44.80, 47.10)
    for (preco in precos) {
        delay(1000)
        emit(preco)
    }
}

fun main() = runBlocking {
    atualizacoesDePreco().collect { preco ->
        println("Preço atual: R$ $preco")
    }
}

Flow é ideal para dados que chegam aos poucos: eventos de UI, atualizações de banco de dados, respostas de WebSocket e qualquer cenário onde você precisa reagir a mudanças contínuas.

StateFlow e SharedFlow

Além do Flow tradicional (cold), Kotlin oferece variantes hot que mantêm estado ou compartilham emissões entre múltiplos coletores:

import kotlinx.coroutines.flow.*

// StateFlow: mantém o último valor emitido
val _contador = MutableStateFlow(0)
val contador: StateFlow<Int> = _contador.asStateFlow()

// SharedFlow: broadcast para múltiplos coletores
val _eventos = MutableSharedFlow<String>()
val eventos: SharedFlow<String> = _eventos.asSharedFlow()

O StateFlow é muito usado em arquiteturas como MVVM no Android, substituindo o LiveData com uma API baseada em coroutines. Já o SharedFlow é ideal para eventos que não devem ser repetidos, como navegação ou exibição de mensagens.

Combinando Flows

Você pode combinar múltiplos Flows para criar fluxos de dados mais complexos:

fun main() = runBlocking {
    val nomes = flowOf("Ana", "Bruno", "Carla")
    val idades = flowOf(25, 30, 28)

    nomes.zip(idades) { nome, idade ->
        "$nome tem $idade anos"
    }.collect { println(it) }
    // Ana tem 25 anos
    // Bruno tem 30 anos
    // Carla tem 28 anos
}

O operador combine é outra opção: ele re-emite sempre que qualquer um dos Flows emite um novo valor, usando o último valor disponível de cada um.

Casos de Uso no Mundo Real

  • Atualizações de interface no Android: usar StateFlow no ViewModel para expor o estado da tela e coletar no Fragment ou Composable, garantindo que a UI sempre reflita o estado mais recente.
  • Busca com debounce: capturar o texto digitado pelo usuário como um Flow e aplicar debounce(300) seguido de distinctUntilChanged() e flatMapLatest para realizar buscas sem sobrecarregar a API.
  • Monitoramento de conexão de rede: criar um Flow que emite o status de conectividade do dispositivo usando callbacks do sistema, permitindo que a aplicação reaja automaticamente a mudanças de rede.
  • Leitura contínua de sensores ou dados IoT: usar Flow para modelar streams de dados de sensores como temperatura, localização GPS ou dados de dispositivos Bluetooth, processando cada leitura conforme ela chega.

Boas Práticas

  • Prefira expor StateFlow ou SharedFlow como tipos imutáveis na API pública e manter o MutableStateFlow ou MutableSharedFlow como privado.
  • Use o operador catch para tratar erros dentro do pipeline do Flow em vez de envolver o collect em um bloco try-catch.
  • Utilize flowOn(Dispatchers.IO) para mover a produção de dados para uma thread de IO, mantendo a coleta na thread principal quando necessário.
  • Evite coletar Flows diretamente em GlobalScope. No Android, prefira lifecycleScope ou repeatOnLifecycle para evitar vazamentos de memória.
  • Use stateIn e shareIn para converter Flows cold em hot quando múltiplos consumidores precisam do mesmo fluxo de dados.

Erros Comuns

  • Coletar o mesmo Flow cold múltiplas vezes sem perceber: cada chamada a collect reinicia a produção do Flow. Se isso não é o comportamento desejado, converta para SharedFlow com shareIn.
  • Esquecer que collect é uma função suspensa: tentar chamar collect fora de uma coroutine causa erro de compilação. Sempre colete dentro de um escopo de coroutine.
  • Bloquear dentro de um flow { }: usar operações bloqueantes como Thread.sleep() dentro do builder flow em vez de delay() trava a coroutine e anula os benefícios da abordagem assíncrona.
  • Não tratar erros no pipeline: se uma exceção ocorre dentro de um Flow e não é capturada com catch, ela se propaga para o coletor e pode derrubar a coroutine inteira.
  • Confundir StateFlow com LiveData: embora ambos mantenham o último valor, StateFlow requer coleta em coroutines e não é lifecycle-aware por padrão. No Android, combine com repeatOnLifecycle.

Perguntas Frequentes

Qual a diferença entre Flow e Sequence em Kotlin? Sequence é síncrono e processa elementos sob demanda na mesma thread. Flow é assíncrono e trabalha com coroutines, permitindo operações suspensas como chamadas de rede e delays entre emissões.

Quando devo usar StateFlow em vez de Flow? Use StateFlow quando precisa manter o último valor emitido e compartilhar entre múltiplos coletores, como o estado de uma tela. Use Flow quando os dados são produzidos sob demanda e cada coletor deve receber seu próprio fluxo independente.

Flow substitui RxJava? Flow cobre a grande maioria dos casos de uso do RxJava para projetos Kotlin, com a vantagem de integração nativa com coroutines. Para projetos novos em Kotlin, Flow é a escolha recomendada.

Posso usar Flow sem coroutines? Não. Flow faz parte do pacote kotlinx.coroutines.flow e depende de coroutines para funcionar. A coleta de um Flow sempre acontece dentro de uma função suspensa.

Termos Relacionados

  • Coroutine — mecanismo de concorrência que sustenta o funcionamento do Flow
  • Suspend — modificador que permite funções assíncronas usadas dentro de Flows
  • Lambda — funções anônimas usadas nos operadores de Flow como map e filter
  • Higher-Order Function — padrão de funções que recebem outras funções, base dos operadores de Flow