O que é emit em Kotlin?

A função emit é o mecanismo principal para enviar valores dentro de um Flow em Kotlin. Quando você cria um Flow usando o builder flow { }, cada chamada a emit(valor) envia um novo valor para o coletor (quem esta consumindo o Flow).

Pense no emit como o momento em que você coloca um item na esteira de producao. Do outro lado, o collect pega cada item conforme ele chega. Essa dinâmica de produtor-consumidor é a essencia do Flow.

Sintaxe básica

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

fun numeros(): Flow<Int> = flow {
    emit(1)
    emit(2)
    emit(3)
}

fun main() = runBlocking {
    numeros().collect { valor ->
        println("Recebido: $valor")
    }
}
// Saida:
// Recebido: 1
// Recebido: 2
// Recebido: 3

Cada emit e uma função suspend, o que significa que ela pode pausar a execução do produtor até que o consumidor esteja pronto para receber o proximo valor.

emit e uma função suspend

O fato de emit ser suspend e fundamental para o comportamento do Flow:

fun dadosComDelay(): Flow<String> = flow {
    emit("Carregando...")
    delay(1000)
    emit("Dados parciais")
    delay(1000)
    emit("Dados completos")
}

fun main() = runBlocking {
    dadosComDelay().collect { status ->
        println("$status - ${System.currentTimeMillis()}")
    }
}

O produtor pode suspender entre emissoes (usando delay ou outras funções suspend), e o consumidor recebe cada valor conforme ele e emitido. Isso cria um fluxo natural de dados ao longo do tempo.

Emitindo em loops

Um padrão comum e emitir valores dentro de um loop:

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

fun fibonacci(): Flow<Long> = flow {
    var a = 0L
    var b = 1L
    while (true) {
        emit(a)
        val temp = a + b
        a = b
        b = temp
    }
}

fun main() = runBlocking {
    // Pegar os 10 primeiros Fibonacci
    fibonacci().take(10).collect { println(it) }
}

O Flow e lazy: o loop infinito em fibonacci() não e um problema porque take(10) cancela o Flow apos coletar 10 valores.

Emitindo a partir de fontes externas

O emit pode ser chamado apos receber dados de qualquer fonte:

fun monitorarArquivo(caminho: String): Flow<String> = flow {
    val arquivo = java.io.File(caminho)
    var ultimaModificacao = 0L

    while (true) {
        val modificacao = arquivo.lastModified()
        if (modificacao != ultimaModificacao) {
            ultimaModificacao = modificacao
            emit(arquivo.readText())
        }
        delay(1000)
    }
}

fun leiturasDeSensor(): Flow<Double> = flow {
    val random = java.util.Random()
    while (true) {
        val leitura = 20.0 + random.nextDouble() * 10.0
        emit(leitura)
        delay(500)
    }
}

Restricoes do emit

O emit tem uma restricao importante: não pode ser chamado de um contexto concorrente. Dentro de um flow { }, você não pode emitir de outra coroutine:

// ERRADO: emit de contexto concorrente
fun fluxoErrado(): Flow<Int> = flow {
    coroutineScope {
        launch {
            emit(1) // IllegalStateException em tempo de execucao!
        }
    }
}

// CORRETO: usar channelFlow para emissao concorrente
fun fluxoConcorrente(): Flow<Int> = channelFlow {
    launch {
        send(1) // OK: channelFlow suporta concorrência
    }
    launch {
        send(2)
    }
}

Se você precisa emitir valores de múltiplas coroutines, use channelFlow com send em vez de flow com emit.

emit com transformacoes

Operadores como transform permitem emitir múltiplos valores para cada valor de entrada:

fun main() = runBlocking {
    val numeros = flowOf(1, 2, 3)

    numeros.transform { valor ->
        emit("Processando $valor...")
        delay(300)
        emit("Resultado: ${valor * valor}")
    }.collect { println(it) }
}
// Saida:
// Processando 1...
// Resultado: 1
// Processando 2...
// Resultado: 4
// Processando 3...
// Resultado: 9

O operador transform e a base sobre a qual map e filter são construidos. Ele da liberdade total sobre quantos valores emitir para cada valor recebido.

Backpressure e emit

O emit naturalmente implementa backpressure. Se o coletor for mais lento que o produtor, o emit suspende até que o coletor esteja pronto:

fun produtorRapido(): Flow<Int> = flow {
    for (i in 1..5) {
        println("Emitindo $i")
        emit(i)
    }
}

fun main() = runBlocking {
    produtorRapido().collect { valor ->
        delay(1000) // Coletor lento
        println("Coletado: $valor")
    }
}
// A emissao espera o coletor processar antes de continuar

Para cenários onde você quer descartar valores ou usar buffer, existem operadores como buffer, conflate e collectLatest.

Quando usar emit

  • Producao de dados sob demanda: quando os dados são gerados dinamicamente e precisam ser consumidos um por vez.
  • Streams de eventos: monitoramento de arquivos, sensores, WebSockets.
  • Transformacoes complexas: quando você precisa emitir múltiplos valores derivados de um único valor de entrada.
  • Conversao de callbacks para Flow: dentro de callbackFlow, você usa trySend (similar ao emit) para converter APIs baseadas em callback para Flow.

Casos de Uso no Mundo Real

  1. Monitoramento de dados em tempo real: aplicações que exibem dashboards com metricas ao vivo (preco de acoes, temperatura de servidores, status de sistemas) utilizam emit dentro de loops com delay para publicar atualizações periodicas. O Flow gerenciado com emit permite que a UI reaja a cada novo valor sem polling manual.

  2. Upload de arquivos com progresso: durante o upload de arquivos grandes, o emit e usado para publicar o percentual de progresso. Um Flow emite valores de 0 a 100 conforme os chunks sao enviados, permitindo que a camada de apresentação atualize uma barra de progresso de forma reativa.

  3. paginação de APIs: ao buscar dados paginados de uma API REST, o emit publica cada pagina de resultados conforme ela e carregada. O consumidor pode processar e exibir os dados incrementalmente sem esperar que todas as paginas sejam carregadas.

  4. Conversao de callbacks legados para Flow: em projetos que migram de APIs baseadas em callback (como listeners do Firebase ou eventos de sensores do Android), o emit (via callbackFlow e trySend) e o mecanismo que transforma esses callbacks em streams reativos compativeis com a arquitetura moderna baseada em coroutines.

Boas Praticas

  • Mantenha a lógica de emissao simples e focada na producao de dados. Transformacoes complexas devem ser feitas com operadores do Flow (map, filter, transform) em vez de lógica condicional elaborada antes de cada emit.
  • Use channelFlow com send em vez de flow com emit quando precisar emitir valores de múltiplas coroutines concorrentes. Tentar emitir de contextos concorrentes dentro de flow {} causa exceção em tempo de execução.
  • Trate exceções que possam ocorrer antes do emit usando o operador catch no pipeline do Flow, em vez de try-catch dentro do builder. Isso mantém a separação entre producao e tratamento de erros.
  • Ao emitir valores em loops infinitos (sensores, polling), sempre inclua um mecanismo de cancelamento cooperativo. O Flow já e cancelavel por natureza, mas certifique-se de que operações bloqueantes sejam interruptiveis.
  • Evite efeitos colaterais dentro do builder flow {} que não estejam diretamente relacionados a producao de dados. Use o operador onEach no lado do consumo para acoes como logging e analytics.

Perguntas Frequentes

P: Posso chamar emit de dentro de uma coroutine lancada com launch ou async? R: Nao, dentro de um builder flow {}. O emit exige que a emissao seja sequencial e no mesmo contexto da coroutine do Flow. Se você precisa emitir de contextos concorrentes, use channelFlow com a função send em vez de flow com emit.

P: O que acontece se o coletor for mais lento que o produtor? R: O emit implementa backpressure naturalmente: ele suspende a execução do produtor até que o coletor termine de processar o valor anterior. Para cenários onde você quer alterar esse comportamento, use operadores como buffer (para adicionar um buffer intermediario), conflate (para descartar valores intermediarios) ou collectLatest (para cancelar o processamento anterior quando um novo valor chega).

P: Qual a diferenca entre emit e trySend em callbackFlow? R: O emit e uma função suspend usada dentro de flow {}, enquanto trySend e uma função não-suspend usada dentro de callbackFlow {}. O trySend e necessário em callbacks porque callbacks tradicionais não sao funções suspend. O trySend retorna um resultado indicando se o envio foi bem-sucedido, sem suspender a thread.

P: Posso emitir null em um Flow? R: Sim, desde que o tipo do Flow permita nulos. Um Flow<String?> aceita emit(null) normalmente. O coletor recebera o valor null como qualquer outro valor emitido.

Erros comuns

  1. Chamar emit fora do flow builder: emit só pode ser chamado dentro do escopo do builder flow { } ou dentro de operadores como transform.

  2. Emitir de contexto concorrente: usar launch ou async dentro de flow { } e tentar emitir. Use channelFlow para isso.

  3. Esquecer que emit e suspend: tratar emit como uma operação instantanea quando na verdade ela pode suspender se o coletor estiver ocupado.

  4. Nao tratar exceções: se uma exceção ocorre antes do emit, os valores já emitidos foram processados, mas os restantes serao perdidos. Use catch no Flow para tratamento adequado.

  5. Confundir emit com send: emit e para flow { }, send e para channelFlow { } e channels. Usar um no contexto do outro causa erro de compilação.

callbackFlow e trySend

Para converter APIs baseadas em callback para Flow, use callbackFlow com trySend:

fun eventosDeClique(botao: Botao): Flow<Unit> = callbackFlow {
    val listener = object : OnClickListener {
        override fun onClick() {
            trySend(Unit)
        }
    }
    botao.adicionarListener(listener)
    awaitClose { botao.removerListener(listener) }
}

Termos relacionados

  • Flow: o tipo de stream reativo do Kotlin onde emit e a função principal de producao.
  • collect: a função terminal que consome os valores emitidos por um Flow.
  • channelFlow: builder de Flow que suporta emissao concorrente usando send.
  • transform: operador que permite emitir múltiplos valores para cada valor de entrada.
  • Coroutine: contexto de execução assíncrono onde Flows operam.
  • suspend: modificador que permite que emit pause a execução quando necessário.

O emit e o coracao do sistema de Flows em Kotlin. Entender como ele funciona, suas restricoes de concorrência e sua relação com backpressure e essencial para construir pipelines de dados reativos eficientes e corretos.