Coroutines são, sem exagero, um dos recursos mais poderosos de Kotlin. Se você já sofreu com callbacks aninhados, threads manuais ou AsyncTask no Android, prepare-se: sua vida vai mudar. Vamos entender como funciona essa mágica.
O que são Coroutines?
Coroutines são uma forma de escrever código assíncrono de maneira sequencial. Em vez de usar callbacks ou encadear Promises, você escreve código que parece síncrono, mas por baixo dos panos executa de forma concorrente e não bloqueante.
Pense assim: uma coroutine é como uma função que pode ser pausada e retomada depois, sem bloquear a thread em que está rodando.
Configuração
Para usar coroutines, adicione a dependência no build.gradle.kts:
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
// Para Android:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
}
Primeiro exemplo: launch e delay
import kotlinx.coroutines.*
fun main() = runBlocking {
println("Início - ${Thread.currentThread().name}")
launch {
delay(1000) // pausa sem bloquear a thread
println("Coroutine 1 finalizou!")
}
launch {
delay(500)
println("Coroutine 2 finalizou!")
}
println("Coroutines disparadas!")
}
Saída:
Início - main
Coroutines disparadas!
Coroutine 2 finalizou!
Coroutine 1 finalizou!
Repare: as duas coroutines rodaram concorrentemente, e a coroutine 2 terminou primeiro porque tinha menos delay. Tudo isso sem criar threads extras.
Suspend Functions
O coração das coroutines são as suspend functions — funções que podem ser suspensas e retomadas:
suspend fun buscarDadosDoServidor(): String {
delay(2000) // simula uma chamada de rede
return "Dados recebidos com sucesso!"
}
suspend fun buscarDoBancoDeDados(): String {
delay(1000) // simula consulta ao banco
return "Dados do banco carregados!"
}
fun main() = runBlocking {
val inicio = System.currentTimeMillis()
// Execução sequencial
val servidor = buscarDadosDoServidor()
val banco = buscarDoBancoDeDados()
println("$servidor | $banco")
println("Tempo sequencial: ${System.currentTimeMillis() - inicio}ms") // ~3000ms
}
async e await: execução paralela
Quando as chamadas são independentes, use async para rodá-las em paralelo:
fun main() = runBlocking {
val inicio = System.currentTimeMillis()
// Execução paralela com async
val servidorDeferred = async { buscarDadosDoServidor() }
val bancoDeferred = async { buscarDoBancoDeDados() }
val servidor = servidorDeferred.await()
val banco = bancoDeferred.await()
println("$servidor | $banco")
println("Tempo paralelo: ${System.currentTimeMillis() - inicio}ms") // ~2000ms
}
A diferença é gritante: de 3 segundos caiu pra 2. O async dispara as duas operações ao mesmo tempo, e o await() espera o resultado.
Dispatchers: controlando onde a coroutine roda
Dispatchers determinam em qual thread (ou pool de threads) a coroutine será executada:
fun main() = runBlocking {
// Thread principal
launch(Dispatchers.Main) {
// Atualizar UI (Android)
}
// Pool de threads otimizado para I/O
launch(Dispatchers.IO) {
// Chamadas de rede, leitura de arquivo, banco de dados
val dados = fazerChamadaDeRede()
}
// Pool otimizado para CPU
launch(Dispatchers.Default) {
// Cálculos pesados, processamento de imagem
val resultado = processarDadosPesados()
}
}
No dia a dia:
Dispatchers.Main: atualizar interface (Android/Desktop)Dispatchers.IO: operações de entrada/saídaDispatchers.Default: trabalho intensivo de CPU
withContext: trocar de dispatcher
Muitas vezes você precisa trocar de contexto dentro da mesma coroutine:
class UsuarioRepository(private val api: ApiService) {
suspend fun buscarUsuario(id: Int): Usuario {
// Faz a chamada de rede em IO
return withContext(Dispatchers.IO) {
api.getUsuario(id)
}
}
}
// No ViewModel (Android):
class UsuarioViewModel : ViewModel() {
fun carregar() {
viewModelScope.launch { // roda em Main por padrão
val usuario = repository.buscarUsuario(42) // muda pra IO internamente
_uiState.value = UiState.Sucesso(usuario) // volta pra Main
}
}
}
Tratamento de erros
Coroutines oferecem tratamento de erros com try/catch convencional:
suspend fun operacaoArriscada(): String {
delay(500)
throw IllegalStateException("Deu ruim!")
}
fun main() = runBlocking {
// Opção 1: try/catch simples
try {
val resultado = operacaoArriscada()
println(resultado)
} catch (e: Exception) {
println("Erro capturado: ${e.message}")
}
// Opção 2: CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
println("Handler capturou: ${exception.message}")
}
val job = CoroutineScope(Dispatchers.Default + handler).launch {
operacaoArriscada()
}
job.join()
}
Cancelamento de coroutines
Uma das belezas das coroutines é o cancelamento cooperativo:
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("Processando item $i...")
delay(100) // ponto de cancelamento
}
}
delay(500)
println("Cansamos, vamos cancelar!")
job.cancelAndJoin()
println("Job cancelado com sucesso!")
}
Todas as suspend functions da biblioteca padrão (delay, yield, etc.) verificam o cancelamento automaticamente. Se você tem loops sem suspend functions, use isActive ou ensureActive().
Exemplo prático: buscar dados de várias APIs
Vamos a um cenário real — buscar dados de múltiplas fontes e combinar:
data class PaginaInicial(
val noticias: List<Noticia>,
val clima: Clima,
val cotacaoDolar: Double
)
suspend fun carregarPaginaInicial(): PaginaInicial = coroutineScope {
val noticias = async { noticiaService.buscarUltimas() }
val clima = async { climaService.buscarPrevisao("São Paulo") }
val cotacao = async { financeiroService.buscarCotacaoDolar() }
PaginaInicial(
noticias = noticias.await(),
clima = clima.await(),
cotacaoDolar = cotacao.await()
)
}
As três chamadas rodam em paralelo e o resultado é combinado. Limpo, eficiente e fácil de entender.
Conclusão
Coroutines são o jeito Kotlin de lidar com concorrência: simples, seguro e poderoso. Uma vez que você pega o jeito, não vai querer voltar pra callbacks nunca mais. O segredo é praticar — comece substituindo chamadas assíncronas no seu projeto por coroutines e veja a diferença na legibilidade do código.
No próximo post, vamos falar sobre Kotlin Flow — o sistema reativo que se integra perfeitamente com coroutines. Até lá!