Se coroutines são o coração da programação assíncrona em Kotlin, Flow é o sistema circulatório. Enquanto uma suspend function retorna um único valor, um Flow emite múltiplos valores ao longo do tempo. É a resposta do Kotlin para programação reativa — e é muito mais simples do que RxJava.

O que é Kotlin Flow?

Flow é uma API de streams assíncronos construída sobre coroutines. Pense nele como uma sequência de valores que são computados de forma assíncrona. Diferente de Sequence (que é síncrono), Flow opera de forma não bloqueante e respeita cancelamento.

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

fun contagem(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(500) // simula trabalho assíncrono
        emit(i)    // emite o próximo valor
    }
}

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

Saída (uma a cada 500ms):

Recebido: 1
Recebido: 2
Recebido: 3
Recebido: 4
Recebido: 5

Flow é frio (cold)

Um conceito fundamental: Flow é cold — ou seja, o código dentro do builder flow { } só executa quando alguém chama collect. Se ninguém coleta, nada acontece:

val meuFlow = flow {
    println("Flow iniciou!") // só executa quando collect() é chamado
    emit(1)
    emit(2)
    emit(3)
}

// Nada aconteceu ainda...
println("Antes do collect")

meuFlow.collect { println(it) }
// Agora sim: "Flow iniciou!", 1, 2, 3

Operadores essenciais

Flow vem com uma coleção rica de operadores para transformar, filtrar e combinar streams:

map e filter

suspend fun main() {
    (1..10).asFlow()
        .filter { it % 2 == 0 }          // só pares
        .map { it * it }                   // eleva ao quadrado
        .collect { println(it) }           // 4, 16, 36, 64, 100
}

transform

Para transformações mais complexas:

fun buscarUsuarios(): Flow<Int> = (1..5).asFlow()

suspend fun main() {
    buscarUsuarios()
        .transform { id ->
            emit("Buscando usuário #$id...")
            val usuario = buscarNoBanco(id) // suspend function
            emit("Encontrado: ${usuario.nome}")
        }
        .collect { println(it) }
}

take, drop e distinctUntilChanged

suspend fun main() {
    val numeros = flowOf(1, 1, 2, 2, 3, 3, 4, 5, 5)

    numeros
        .distinctUntilChanged() // remove duplicatas consecutivas
        .drop(1)                // pula o primeiro
        .take(3)                // pega apenas 3
        .collect { println(it) } // 2, 3, 4
}

Combinando Flows

zip: combinar par a par

suspend fun main() {
    val nomes = flowOf("Karina", "João", "Ana")
    val linguagens = flowOf("Kotlin", "Java", "Python")

    nomes.zip(linguagens) { nome, linguagem ->
        "$nome programa em $linguagem"
    }.collect { println(it) }

    // Karina programa em Kotlin
    // João programa em Java
    // Ana programa em Python
}

combine: combinar com o valor mais recente

val pesquisa = MutableStateFlow("")
val filtro = MutableStateFlow("todos")

val resultados: Flow<List<Produto>> = combine(pesquisa, filtro) { termo, tipo ->
    repository.buscar(termo, tipo)
}

O combine re-emite sempre que qualquer um dos flows muda — perfeito para buscas com filtros na UI.

flatMapLatest: cancelar o anterior

val termoDeBusca = MutableStateFlow("")

val resultados = termoDeBusca
    .debounce(300) // espera 300ms sem digitação
    .distinctUntilChanged()
    .flatMapLatest { termo ->
        if (termo.isBlank()) flowOf(emptyList())
        else buscarProdutos(termo) // cancela a busca anterior automaticamente
    }

StateFlow e SharedFlow

Enquanto Flow é cold, StateFlow e SharedFlow são hot — emitem valores independente de ter collectors.

StateFlow: estado observável

class CarrinhoViewModel : ViewModel() {
    private val _itens = MutableStateFlow<List<ItemCarrinho>>(emptyList())
    val itens: StateFlow<List<ItemCarrinho>> = _itens.asStateFlow()

    val total: StateFlow<Double> = _itens
        .map { lista -> lista.sumOf { it.preco * it.quantidade } }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = 0.0
        )

    fun adicionar(produto: Produto) {
        _itens.update { listaAtual ->
            listaAtual + ItemCarrinho(produto.nome, produto.preco, 1)
        }
    }
}

StateFlow sempre tem um valor (nunca é vazio) e só emite quando o valor muda. É a substituição moderna do LiveData no Android.

SharedFlow: eventos

class NotificacaoViewModel : ViewModel() {
    private val _eventos = MutableSharedFlow<Evento>()
    val eventos: SharedFlow<Evento> = _eventos.asSharedFlow()

    fun mostrarMensagem(texto: String) {
        viewModelScope.launch {
            _eventos.emit(Evento.Snackbar(texto))
        }
    }

    fun navegar(rota: String) {
        viewModelScope.launch {
            _eventos.emit(Evento.Navegacao(rota))
        }
    }
}

sealed class Evento {
    data class Snackbar(val mensagem: String) : Evento()
    data class Navegacao(val rota: String) : Evento()
}

SharedFlow é ideal para eventos que não devem ser “consumidos de novo” ao recompor a UI.

Flow no Android com Compose

A integração entre Flow e Jetpack Compose é perfeita:

@Composable
fun TelaCarrinho(viewModel: CarrinhoViewModel = viewModel()) {
    val itens by viewModel.itens.collectAsStateWithLifecycle()
    val total by viewModel.total.collectAsStateWithLifecycle()

    Column(modifier = Modifier.padding(16.dp)) {
        Text("Carrinho de Compras", style = MaterialTheme.typography.headlineMedium)

        LazyColumn(modifier = Modifier.weight(1f)) {
            items(itens) { item ->
                ListItem(
                    headlineContent = { Text(item.nome) },
                    trailingContent = { Text("R$ ${"%.2f".format(item.preco)}") }
                )
            }
        }

        HorizontalDivider()
        Text(
            text = "Total: R$ ${"%.2f".format(total)}",
            style = MaterialTheme.typography.titleLarge,
            modifier = Modifier.padding(top = 8.dp)
        )
    }
}

O collectAsStateWithLifecycle() é lifecycle-aware: para de coletar quando o app vai pra background e retoma quando volta. Essencial para economizar bateria.

Tratamento de erros

fun dadosDoServidor(): Flow<Dados> = flow {
    val resultado = api.buscar()
    emit(resultado)
}.catch { exception ->
    // Trata o erro e pode emitir um valor de fallback
    println("Erro: ${exception.message}")
    emit(Dados.vazio())
}.onStart {
    println("Iniciando busca...")
}.onCompletion { causa ->
    if (causa == null) println("Completou com sucesso")
    else println("Completou com erro: ${causa.message}")
}

Boas práticas

  1. Use StateFlow para estado de UI, SharedFlow para eventos
  2. stateIn com WhileSubscribed: economiza recursos quando não há observers
  3. collectAsStateWithLifecycle: sempre no Android para lifecycle awareness
  4. flowOn para mudar dispatcher: flow.flowOn(Dispatchers.IO) em vez de mudar no collect
  5. Evite collect dentro de collect: use operadores como flatMapLatest ou combine

Conclusão

Kotlin Flow é programação reativa do jeito certo: simples, integrada com coroutines e sem a complexidade do RxJava. Se você trabalha com Android, dominar Flow é essencial em 2026. Para backend, Flow brilha em cenários de streaming e processamento de dados em tempo real.

Hora de deixar seus dados fluírem!