O que e Callback em Kotlin?
Um callback e uma funcao passada como argumento para outra funcao, que sera executada em um momento posterior – geralmente quando uma operacao assincrona termina ou quando um evento especifico ocorre. Em Kotlin, callbacks sao implementados de forma elegante usando lambdas, funcoes de ordem superior e interfaces funcionais.
O conceito e antigo e existe em praticamente todas as linguagens, mas em Kotlin ele ganha uma sintaxe limpa e concisa que torna o codigo muito mais legivel do que em linguagens como Java.
Callback basico com lambda
A forma mais simples de callback em Kotlin e passar uma lambda como parametro:
fun buscarDados(onSucesso: (String) -> Unit, onErro: (Exception) -> Unit) {
try {
// Simula busca de dados
val resultado = "Dados carregados"
onSucesso(resultado)
} catch (e: Exception) {
onErro(e)
}
}
fun main() {
buscarDados(
onSucesso = { dados -> println("Sucesso: $dados") },
onErro = { erro -> println("Erro: ${erro.message}") }
)
}
Aqui, onSucesso e onErro sao callbacks. A funcao buscarDados recebe as duas lambdas e chama a apropriada dependendo do resultado da operacao.
Callback com interface funcional
Em cenarios de interoperabilidade com Java ou quando voce quer um contrato mais explicito, voce pode usar interfaces funcionais:
fun interface OnResultadoListener {
fun onResultado(dados: String)
}
class Repositorio {
fun carregar(listener: OnResultadoListener) {
// Simula processamento
val resultado = "Dados do servidor"
listener.onResultado(resultado)
}
}
fun main() {
val repo = Repositorio()
// Usando SAM conversion
repo.carregar { dados ->
println("Recebido: $dados")
}
// Ou de forma explicita
repo.carregar(object : OnResultadoListener {
override fun onResultado(dados: String) {
println("Recebido: $dados")
}
})
}
A palavra-chave fun interface permite que o Kotlin converta automaticamente uma lambda em uma implementacao da interface (SAM conversion), mantendo o codigo conciso.
Callback para eventos de UI
Um uso classico de callbacks e em componentes de interface grafica, como botoes:
class Botao {
private var onClickListener: (() -> Unit)? = null
fun setOnClickListener(listener: () -> Unit) {
onClickListener = listener
}
fun clicar() {
onClickListener?.invoke()
}
}
fun main() {
val botao = Botao()
botao.setOnClickListener {
println("Botao foi clicado!")
}
botao.clicar() // Botao foi clicado!
}
Esse padrao e fundamental no desenvolvimento Android, onde callbacks conectam acoes do usuario a logica de negocio.
Callback hell e como evitar
O problema classico com callbacks e o callback hell – aninhamento excessivo que torna o codigo ilegivel:
// Callback hell -- evite isso
fun carregarTudo() {
buscarUsuario { usuario ->
buscarPedidos(usuario.id) { pedidos ->
buscarDetalhes(pedidos.first().id) { detalhes ->
buscarEndereco(detalhes.enderecoId) { endereco ->
println("Endereco: $endereco")
}
}
}
}
}
Em Kotlin, a solucao moderna para esse problema sao as coroutines, que permitem escrever codigo assincrono de forma sequencial:
// Com coroutines -- muito mais limpo
suspend fun carregarTudo() {
val usuario = buscarUsuario()
val pedidos = buscarPedidos(usuario.id)
val detalhes = buscarDetalhes(pedidos.first().id)
val endereco = buscarEndereco(detalhes.enderecoId)
println("Endereco: $endereco")
}
Convertendo callbacks para coroutines
Quando voce trabalha com APIs baseadas em callbacks (especialmente bibliotecas Java), pode converte-las para suspending functions usando suspendCoroutine ou suspendCancellableCoroutine:
import kotlinx.coroutines.*
import kotlin.coroutines.*
// API legada baseada em callback
fun buscarDadosLegado(callback: (Result<String>) -> Unit) {
// Simula operacao assincrona
callback(Result.success("Dados legados"))
}
// Wrapper com coroutines
suspend fun buscarDadosSuspend(): String = suspendCancellableCoroutine { continuation ->
buscarDadosLegado { resultado ->
resultado
.onSuccess { continuation.resume(it) }
.onFailure { continuation.resumeWithException(it) }
}
}
fun main() = runBlocking {
val dados = buscarDadosSuspend()
println(dados)
}
Essa tecnica e extremamente util ao migrar codigo legado para coroutines de forma incremental.
Callback com tipo generico
Voce pode criar callbacks reutilizaveis usando generics:
typealias Callback<T> = (Result<T>) -> Unit
fun <T> executarAsync(operacao: () -> T, callback: Callback<T>) {
try {
val resultado = operacao()
callback(Result.success(resultado))
} catch (e: Exception) {
callback(Result.failure(e))
}
}
fun main() {
executarAsync(
operacao = { 42 * 2 },
callback = { resultado ->
resultado
.onSuccess { println("Resultado: $it") }
.onFailure { println("Erro: ${it.message}") }
}
)
}
O typealias torna a assinatura mais legivel, e o uso de Result padroniza o tratamento de sucesso e erro.
Quando usar callbacks
Callbacks ainda sao uteis em varios cenarios:
- Interoperabilidade com Java: muitas bibliotecas Java usam o padrao listener/callback, e voce precisa se adaptar.
- Eventos de UI: cliques, gestos e outros eventos de interface sao naturalmente modelados como callbacks.
- APIs simples: quando a operacao e direta e nao ha aninhamento, um callback e perfeitamente adequado.
- Bibliotecas e frameworks: se voce esta criando uma biblioteca que precisa ser usada por projetos que nao usam coroutines.
Porem, para logica assincrona complexa com multiplas etapas, prefira coroutines. Elas eliminam o aninhamento e facilitam o tratamento de erros com try-catch normal.
Erros comuns
Callback hell: aninhar callbacks excessivamente. Use coroutines ou quebre a logica em funcoes menores.
Esquecer de chamar o callback: se a funcao tem caminhos de execucao diferentes (sucesso, erro, timeout), certifique-se de que o callback e chamado em todos eles.
Chamar o callback mais de uma vez: a maioria dos contratos espera que o callback seja chamado exatamente uma vez. Chamar multiplas vezes pode causar comportamento inesperado.
Nao tratar erros: passar apenas um callback de sucesso e ignorar falhas. Sempre forneca um mecanismo para tratar erros.
Reter referencias de callback: em Android, manter uma referencia a um callback que contem uma referencia a uma Activity pode causar memory leaks. Use
WeakReferenceou cancele o callback quando o componente for destruido.
Termos relacionados
- Lambda: a forma mais comum de expressar callbacks em Kotlin, usando a sintaxe
{ parametros -> corpo }. - Higher-Order Function: funcoes que recebem ou retornam outras funcoes, o mecanismo que permite callbacks em Kotlin.
- Coroutine: alternativa moderna aos callbacks para programacao assincrona, eliminando o callback hell.
- suspend: modificador que transforma uma funcao em uma funcao suspensa, substituindo callbacks por retorno direto.
- Flow: stream reativo que substitui callbacks repetitivos por um fluxo de dados continuo.
- SAM Conversion: mecanismo que converte lambdas em implementacoes de interfaces funcionais Java.
Callbacks sao um dos padroes mais fundamentais da programacao. Em Kotlin, a combinacao de lambdas, funcoes de ordem superior e interfaces funcionais torna sua implementacao elegante e expressiva, enquanto coroutines oferecem uma evolucao natural para cenarios mais complexos.