O que e emit em Kotlin?
A funcao emit e o mecanismo principal para enviar valores dentro de um Flow em Kotlin. Quando voce 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 voce coloca um item na esteira de producao. Do outro lado, o collect pega cada item conforme ele chega. Essa dinamica de produtor-consumidor e a essencia do Flow.
Sintaxe basica
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 funcao suspend, o que significa que ela pode pausar a execucao do produtor ate que o consumidor esteja pronto para receber o proximo valor.
emit e uma funcao 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 funcoes 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 padrao 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() nao 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: nao pode ser chamado de um contexto concorrente. Dentro de um flow { }, voce nao 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 concorrencia
}
launch {
send(2)
}
}
Se voce precisa emitir valores de multiplas coroutines, use channelFlow com send em vez de flow com emit.
emit com transformacoes
Operadores como transform permitem emitir multiplos 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 sao 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 ate 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 cenarios onde voce quer descartar valores ou usar buffer, existem operadores como buffer, conflate e collectLatest.
Quando usar emit
- Producao de dados sob demanda: quando os dados sao gerados dinamicamente e precisam ser consumidos um por vez.
- Streams de eventos: monitoramento de arquivos, sensores, WebSockets.
- Transformacoes complexas: quando voce precisa emitir multiplos valores derivados de um unico valor de entrada.
- Conversao de callbacks para Flow: dentro de
callbackFlow, voce usatrySend(similar ao emit) para converter APIs baseadas em callback para Flow.
Erros comuns
Chamar emit fora do flow builder:
emitso pode ser chamado dentro do escopo do builderflow { }ou dentro de operadores comotransform.Emitir de contexto concorrente: usar
launchouasyncdentro deflow { }e tentar emitir. UsechannelFlowpara isso.Esquecer que emit e suspend: tratar emit como uma operacao instantanea quando na verdade ela pode suspender se o coletor estiver ocupado.
Nao tratar excecoes: se uma excecao ocorre antes do emit, os valores ja emitidos foram processados, mas os restantes serao perdidos. Use
catchno Flow para tratamento adequado.Confundir emit com send:
emite paraflow { },sende parachannelFlow { }e channels. Usar um no contexto do outro causa erro de compilacao.
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
emite a funcao principal de producao. - collect: a funcao terminal que consome os valores emitidos por um Flow.
- channelFlow: builder de Flow que suporta emissao concorrente usando
send. - transform: operador que permite emitir multiplos valores para cada valor de entrada.
- Coroutine: contexto de execucao assincrono onde Flows operam.
- suspend: modificador que permite que
emitpause a execucao quando necessario.
O emit e o coracao do sistema de Flows em Kotlin. Entender como ele funciona, suas restricoes de concorrencia e sua relacao com backpressure e essencial para construir pipelines de dados reativos eficientes e corretos.