DisposableEffect — liberação de recursos no Jetpack Compose

Autor: IT Sectr Publicado: 2026-06-30 Tempo de leitura: 9 min

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 — API de side-effect para configuração e liberação garantida de recursos.
  • onDispose — bloco obrigatório executado ao sair da composição ou mudar a chave.
  • Síncrono — ao contrário do LaunchedEffect, o DisposableEffect executa de forma síncrona sem corrotinas.
  • Limpeza — cenários típicos: cancelar assinatura do LiveData, fechar sockets, cancelar registro de BroadcastReceiver.
  • Chaves — ao mudar a chave, onDispose é executado para o valor antigo e reinicialização ocorre com o novo.

O que é DisposableEffect no Jetpack Compose

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.

kotlin
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")
}

Como o DisposableEffect funciona com onDispose

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.

kotlin
// 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)
    }
}

DisposableEffect vs vazamentos de memória

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.

RecursoO que DisposableEffect fazSem DisposableEffect
BroadcastReceiverregister + onDispose → unregisterReceptor permanece ativo
SensorManagerregisterListener + onDispose → unregisterListenerSensor continua enviando dados
Observable (não Flow)subscribe + onDispose → unsubscribeCallback mantém referência
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackVazamento de Callback
Socket / Channelopen + onDispose → closeConexão permanece aberta

Assinatura de sensores via DisposableEffect

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.

kotlin
@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")
}

Registro de BroadcastReceiver via DisposableEffect

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.

kotlin
@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) { ... }
}

Erros comuns com DisposableEffect

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

Qual a diferença entre DisposableEffect e LaunchedEffect?

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.

O bloco onDispose é obrigatório no DisposableEffect?

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.

Como lidar com erros dentro do DisposableEffect?

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.

Posso usar DisposableEffect para assinar Flow?

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.

Quantos DisposableEffects podem existir em um composable?

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

  • DisposableEffect — API de side-effect do Jetpack Compose para inicialização síncrona com limpeza garantida via onDispose.
  • onDispose — bloco obrigatório executado ao sair da composição ou mudar a chave, prevenindo vazamentos de memória.
  • Chaves — quando uma chave muda, onDispose é executado primeiro para o valor antigo, depois a reinicialização ocorre com o novo.
  • Síncrono — DisposableEffect executa de forma síncrona; funções suspend não estão disponíveis dentro dele.
  • Cenários típicos — BroadcastReceiver, sensores, listeners nativos, bibliotecas baseadas em callback, integração AndroidView.
  • Vazamentos — DisposableEffect reduz o número de vazamentos em 60–70% comparado ao gerenciamento manual via callbacks de ciclo de vida.
  • Erros — principais riscos: chamada condicional de onDispose, uso para operações assíncronas, criação de objetos sem remember dentro do efeito.

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.

Discutir o projeto

Leia também