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
- Use
StateFlowpara estado de UI,SharedFlowpara eventos stateIncomWhileSubscribed: economiza recursos quando não há observerscollectAsStateWithLifecycle: sempre no Android para lifecycle awarenessflowOnpara mudar dispatcher:flow.flowOn(Dispatchers.IO)em vez de mudar no collect- Evite
collectdentro decollect: use operadores comoflatMapLatestoucombine
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!