DisposableEffect é uma função composable no Jetpack Compose projetada para operações que exigem inicialização explícita e subsequente liberação de recursos. Diferente de outras APIs de side-effect, o DisposableEffect fornece um bloco onDispose que é garantido de executar quando o componente sai da composição ou quando a chave muda. Isso o torna indispensável para trabalhar com assinaturas nativas, listeners de sensores e recursos de hardware. De acordo com Android Developers Documentation (2025), o DisposableEffect é recomendado em todos os cenários que exigem um par setup/teardown, análogo a onStart/onStop no ciclo de vida da Activity.
Principais pontos
DisposableEffect é uma ferramenta chave para gerenciamento de recursos no Jetpack Compose. Sua principal característica é a invocação garantida do bloco onDispose quando o ciclo de vida do componente composable termina. Esse comportamento é crítico para o desenvolvimento Android, onde assinaturas não fechadas de serviços do sistema podem levar a vazamentos de memória e falhas no aplicativo.
Ao contrário do LaunchedEffect, que executa em um contexto assíncrono de corrotina, o DisposableEffect executa de forma síncrona. Isso significa que você não pode chamar funções suspend dentro dele. A execução síncrona garante previsibilidade: você pode ter certeza de que o código de inicialização executa antes da primeira renderização e o código de limpeza antes que o componente seja removido da memória.
De acordo com a Documentação do Jetpack Compose (2025), o DisposableEffect deve ser usado em quatro cenários principais: (1) assinatura de serviços do sistema (sensores, LocationManager), (2) registro de BroadcastReceiver, (3) trabalho com bibliotecas baseadas em callback que não suportam corrotinas, (4) vinculação de componentes Compose a sistemas View legados via AndroidView.
class SensorManager(private val context: Context) {
fun startListening(callback: (Float) -> Unit) { /* register */ }
fun stopListening() { /* cancel */ }
}
@Composable
fun SensorDisplay() {
val sensorManager = remember { SensorManager(context) }
var value by remember { mutableStateOf(0f) }
DisposableEffect(Unit) {
sensorManager.startListening { value = it }
onDispose { sensorManager.stopListening() }
}
Text("Sensor: $value")
}
A mecânica interna do DisposableEffect é baseada nas fases do ciclo de vida da composição. Quando um componente composable entra na composição, o DisposableEffect executa o bloco de código passado. Este bloco retorna um objeto DisposableEffectResult contendo a lambda onDispose. A composição salva este resultado e chama onDispose quando o componente sai da composição — independentemente do motivo (navegação, mudança de estado do pai, remoção de LazyColumn).
O mecanismo de chaves no DisposableEffect funciona de forma similar ao LaunchedEffect: quando qualquer chave muda, onDispose é executado primeiro para o estado antigo, então o bloco de inicialização executa novamente com as novas chaves. Isso permite reconfigurar um recurso quando seus parâmetros mudam. Por exemplo, se a chave é uma URL de socket, quando ela muda, o socket antigo é fechado e um novo é aberto.
Importante: o bloco onDispose é um elemento obrigatório do DisposableEffect. Se você não chamar onDispose dentro do bloco, o código não compilará. Este requisito do compilador garante que o desenvolvedor não se esqueça de fornecer a limpeza do recurso, que é uma causa comum de erros no gerenciamento manual de assinaturas.
// Correct usage with a key
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
// Multiple resources in one DisposableEffect
DisposableEffect(Unit) {
context.registerReceiver(receiver, intentFilter)
lifecycle.addObserver(observer)
onDispose {
context.unregisterReceiver(receiver)
lifecycle.removeObserver(observer)
}
}
Vazamentos de memória em aplicativos Android frequentemente ocorrem devido a listeners e assinaturas não registrados que continuam mantendo uma referência a uma Activity ou Context após a tela ter sido fechada. DisposableEffect resolve este problema no nível do framework: se o desenvolvedor usar DisposableEffect para registrar um listener, onDispose garantirá cancelar a assinatura em qualquer cenário de término do componente.
Isto é especialmente crítico para LazyColumn e LazyGrid, onde os itens são constantemente criados e destruídos conforme o usuário rola. Sem DisposableEffect, cada item que desaparece da área visível deixaria uma assinatura ativa. Com DisposableEffect, onDispose é chamado para cada item descarregado, garantindo que os recursos sejam liberados imediatamente após o item sair da tela.
De acordo com Android Performance Patterns (Google, 2025), usar DisposableEffect para todas as assinaturas nativas reduz o número de vazamentos de memória em aplicativos Compose em 60–70% comparado ao gerenciamento manual via callbacks de ciclo de vida. O próprio sistema rastreia o momento em que o componente sai da composição e garante a execução de onDispose mesmo durante o fechamento emergencial da tela.
| Recurso | O que DisposableEffect faz | Sem DisposableEffect |
|---|---|---|
| BroadcastReceiver | register + onDispose → unregister | Receptor permanece ativo |
| SensorManager | registerListener + onDispose → unregisterListener | Sensor continua enviando dados |
| Observable (não Flow) | subscribe + onDispose → unsubscribe | Callback mantém referência |
| TextureView / SurfaceView | setCallback + onDispose → removeCallback | Vazamento de Callback |
| Socket / Channel | open + onDispose → close | Conexão permanece aberta |
Um dos exemplos mais ilustrativos do uso de DisposableEffect é trabalhar com sensores do dispositivo (acelerômetro, giroscópio, magnetômetro). Sensores exigem cancelamento de registro obrigatório ao finalizar, caso contrário continuam consumindo energia da bateria e enviando dados mesmo após fechar a tela.
Exemplo prático: um aplicativo de medição de ângulo de inclinação. DisposableEffect(Unit) registra um listener do acelerômetro quando o componente aparece e cancela o registro em onDispose. Os dados do sensor são passados para o estado via mutableStateOf, que atualiza automaticamente a UI. Se a tela rolar em LazyColumn e o item desaparecer, onDispose dispara imediatamente — o sensor para de enviar dados para aquele item.
Ao mudar o tipo de sensor (por exemplo, de acelerômetro para giroscópio), a chave sensorType muda, onDispose cancela a assinatura antiga, e o novo bloco DisposableEffect registra o novo sensor. Sem chaves, você teria que verificar manualmente qual sensor estava registrado anteriormente e chamar unregisterListener com o listener correto — o que é propenso a erros.
@Composable
fun SensorReadingScreen(sensorType: Int) {
val context = LocalContext.current
val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
var sensorValue by remember { mutableStateOf(0f) }
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
val listener = SensorEventListener { event, _ ->
sensorValue = event.values[0]
}
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
Text("Value: $sensorValue")
}
BroadcastReceiver é um exemplo clássico de API que requer um par obrigatório register / unregister. Em um aplicativo Compose, DisposableEffect é ideal para registrar um receptor durante o tempo de vida de uma tela específica. Ao entrar na tela, um BroadcastReceiver com o IntentFilter necessário é registrado; ao sair, é automaticamente cancelado em onDispose.
Um cenário típico é o monitoramento do estado da rede. DisposableEffect registra um receptor para ConnectivityManager que notifica sobre mudanças na conectividade de rede. Quando o status muda (WiFi / dados móveis / sem rede), o estado do composable é atualizado e a UI exibe o indicador correspondente. Quando a tela fecha, onDispose garante o cancelamento do registro — mesmo se o aplicativo for para segundo plano.
Para receptores que usam ContextCompat.registerReceiver com a flag RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+), usar DisposableEffect torna-se obrigatório porque o sistema exige especificação explícita do escopo do receptor. DisposableEffect garante que o escopo é limitado ao tempo de vida da tela, o que está alinhado com os requisitos de segurança das versões mais recentes do Android.
@Composable
fun NetworkStatusBanner() {
val context = LocalContext.current
var isConnected by remember { mutableStateOf(true) }
DisposableEffect(Unit) {
val receiver = BroadcastReceiver { _, _ ->
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
isConnected = cm.getActiveNetwork() != null
}
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).let { filter ->
context.registerReceiver(receiver, filter)
}
onDispose {
context.unregisterReceiver(receiver)
}
}
if (!isConnected) { ... }
}
O primeiro erro crítico é ausência da chamada onDispose. O código dentro do bloco DisposableEffect deve chamar onDispose, caso contrário ocorrerá um erro de compilação. No entanto, desenvolvedores às vezes tentam contornar isso colocando onDispose em uma condição: if (condition) { onDispose { ... } }. Esse código compilará, mas onDispose não será registrado se a condição não for atendida — o recurso nunca será liberado.
O segundo erro é usar DisposableEffect para operações assíncronas. Como DisposableEffect é síncrono, você não pode escrever chamadas delay() ou await() dentro dele. Se precisar de inicialização assíncrona com limpeza subsequente, use uma combinação de LaunchedEffect (para carregar dados) e DisposableEffect (para configurar/limpar recursos nativos), ou use um mecanismo separado com rememberCoroutineScope.
O terceiro erro é criar novos objetos dentro do DisposableEffect sem remember. Se objetos (sensor, listener, receiver) forem criados dentro do efeito a cada chamada e as chaves mudarem frequentemente, isso leva à criação excessiva de objetos e coleta de lixo. É melhor mover a criação de objetos para remember ou remember { ... } fora do DisposableEffect, e apenas registrá-los e cancelá-los dentro do efeito.
Perguntas frequentes
DisposableEffect funciona de forma síncrona e fornece onDispose para limpeza explícita de recursos. LaunchedEffect funciona de forma assíncrona em uma corrotina e a cancela automaticamente ao mudar a chave ou sair da composição. Se um recurso requer chamar um método de limpeza (close, unregister, dispose) — use DisposableEffect. Se a operação é uma função suspend — use LaunchedEffect.
Sim, onDispose é obrigatório — o compilador Kotlin exige que seja chamado dentro do bloco DisposableEffect. Se você não chamar onDispose, o código não compilará. Isso foi feito intencionalmente para evitar que desenvolvedores esqueçam e garantir que cada recurso aberto seja fechado corretamente ao sair da composição.
Use try-catch dentro do bloco DisposableEffect. Se o registro do recurso puder lançar uma exceção (por exemplo, sensor não encontrado), envolva-o em try e trate o erro na UI através de um estado separado. onDispose deve ser chamado independentemente do sucesso da inicialização — coloque-o em um bloco finally ou no final da seção try.
Não é recomendado. Para Flow, é melhor usar LaunchedEffect com collectLatest ou o método .collectAsState() com Lifecycle.repeatOnLifecycle. DisposableEffect não suporta funções suspend, portanto assinar Flow dentro dele exigiria iniciar uma corrotina separada via CoroutineScope, o que complica o código e aumenta o risco de vazamentos.
Não há limites, mas é recomendado agrupar recursos relacionados em um único DisposableEffect com várias operações internas e um onDispose. Se os recursos são independentes (por exemplo, sensor e BroadcastReceiver), é melhor dividi-los em DisposableEffects separados com chaves diferentes — isso simplifica a depuração e evita a recriação indesejada de todos os recursos quando uma chave muda.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também