O que é Callback em Kotlin?
Um callback é uma função passada como argumento para outra função, que sera executada em um momento posterior – geralmente quando uma operação assíncrona termina ou quando um evento específico ocorre. Em Kotlin, callbacks são implementados de forma elegante usando lambdas, funções 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 código muito mais legivel do que em linguagens como Java.
Callback básico 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 são callbacks. A função buscarDados recebe as duas lambdas e chama a apropriada dependendo do resultado da operação.
Callback com interface funcional
Em cenários de interoperabilidade com Java ou quando você quer um contrato mais explicito, você 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 implementação da interface (SAM conversion), mantendo o código conciso.
Callback para eventos de UI
Um uso clássico 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 padrão e fundamental no desenvolvimento Android, onde callbacks conectam acoes do usuário a lógica de negócio.
Callback hell e como evitar
O problema clássico com callbacks e o callback hell – aninhamento excessivo que torna o código 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 solução moderna para esse problema são as coroutines, que permitem escrever código assíncrono 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 você 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 assíncrona
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 técnica e extremamente útil ao migrar código legado para coroutines de forma incremental.
Callback com tipo generico
Você 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 são úteis em vários cenários:
- Interoperabilidade com Java: muitas bibliotecas Java usam o padrão listener/callback, e você precisa se adaptar.
- Eventos de UI: cliques, gestos e outros eventos de interface são naturalmente modelados como callbacks.
- APIs simples: quando a operação e direta e não há aninhamento, um callback e perfeitamente adequado.
- Bibliotecas e frameworks: se você esta criando uma biblioteca que precisa ser usada por projetos que não usam coroutines.
Porem, para lógica assíncrona complexa com múltiplas etapas, prefira coroutines. Elas eliminam o aninhamento é fácilitam o tratamento de erros com try-catch normal.
Casos de Uso no Mundo Real
Listeners de eventos em Android: toda interação do usuário com a interface – cliques em botoes, mudancas em campos de texto, gestos de swipe – e modelada como callback. O
setOnClickListenerdo Android e o exemplo mais clássico, onde uma lambda e registrada para ser executada quando o usuário interage com o componente.Respostas de APIs de rede: bibliotecas como Retrofit e OkHttp usam callbacks para notificar quando uma requisicao HTTP foi concluida. O callback recebe a resposta do servidor ou um objeto de erro, permitindo que o código que fez a requisicao processe o resultado sem bloquear a thread principal.
Observadores de ciclo de vida: no Android, callbacks como
onStart(),onResume(),onPause()eonDestroy()notificam o componente sobre mudancas no ciclo de vida. Frameworks como o Lifecycle do AndroidX usam esse padrão para que bibliotecas reajam automaticamente a mudancas de estado da Activity ou Fragment.Processamento de arquivos e streams: operações de leitura/escrita em arquivos grandes frequentemente usam callbacks para reportar progresso. Em vez de bloquear a thread até a operação terminar, o sistema notifica o chamador a cada bloco processado, permitindo exibir barras de progresso ou cancelar a operação.
Boas Praticas
- Prefira lambdas com parametros nomeados na assinatura da função (
onSucesso,onErro) em vez de um único callback generico. Nomes explicitos tornam a intencao clara e facilitam a leitura do código no ponto de chamada. - Garanta que o callback seja chamado em exatamente um caminho de execução. Esquecer de chamar o callback em algum branch condicional causa bugs silenciosos, e chama-lo mais de uma vez pode causar comportamento inesperado.
- Ao trabalhar com callbacks em Android, sempre considere o ciclo de vida do componente. Cancele ou remova callbacks quando a Activity ou Fragment for destruído para evitar memory leaks e crashes por referência a contextos invalidos.
- Para APIs novas, prefira coroutines e
suspend funem vez de callbacks. UsesuspendCancellableCoroutinepara adaptar APIs legadas baseadas em callback para o mundo das coroutines, facilitando composicao e tratamento de erros. - Utilize
typealiaspara dar nomes significativos a tipos de callback complexos, comotypealias OnResultado<T> = (Result<T>) -> Unit. Isso melhora a legibilidade das assinaturas de funções sem adicionar overhead.
Perguntas Frequentes
P: Quando devo usar callbacks em vez de coroutines em Kotlin? R: Use callbacks quando estiver trabalhando com interoperabilidade Java (bibliotecas que já usam o padrão listener), eventos de UI simples (cliques, gestos) ou quando estiver criando bibliotecas que precisam ser acessiveis a projetos que não usam coroutines. Para lógica assíncrona com múltiplas etapas, prefira coroutines.
P: Como evitar memory leaks com callbacks em Android?
R: Registre callbacks em onStart() ou onResume() e remova-os em onStop() ou onPause(). Evite usar lambdas que capturam referência a Activities ou Views. Se necessário, use WeakReference ou migre para LiveData/Flow que respeitam o ciclo de vida automaticamente.
P: Qual a diferenca entre um callback e um listener em Kotlin? R: Na prática, sao o mesmo conceito. “Listener” e o termo mais comum no ecossistema Android e Java, geralmente implementado como uma interface com um ou mais métodos. “Callback” e o termo generico da programacao para qualquer função passada como argumento para ser chamada posteriormente. Em Kotlin, ambos podem ser implementados como lambdas.
P: E possível converter qualquer API baseada em callback para coroutines?
R: Sim, usando suspendCancellableCoroutine. Voce encapsula a chamada da API legada dentro dessa função, e no callback de sucesso chama continuation.resume(valor), e no callback de erro chama continuation.resumeWithException(exceção). Isso transforma a API em uma suspend fun que pode ser chamada de forma sequencial.
Erros comuns
Callback hell: aninhar callbacks excessivamente. Use coroutines ou quebre a lógica em funções menores.
Esquecer de chamar o callback: se a função tem caminhos de execução 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 múltiplas 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 referências de callback: em Android, manter uma referência a um callback que contém uma referência a uma Activity pode causar memory leaks. Use
WeakReferenceou cancele o callback quando o componente for destruído.
Termos relacionados
- Lambda: a forma mais comum de expressar callbacks em Kotlin, usando a sintaxe
{ parametros -> corpo }. - Higher-Order Function: funções que recebem ou retornam outras funções, o mecanismo que permite callbacks em Kotlin.
- Coroutine: alternativa moderna aos callbacks para programação assíncrona, eliminando o callback hell.
- suspend: modificador que transforma uma função em uma função 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 são um dos padrões mais fundamentais da programação. Em Kotlin, a combinacao de lambdas, funções de ordem superior e interfaces funcionais torna sua implementação elegante e expressiva, enquanto coroutines oferecem uma evolução natural para cenários mais complexos.