DisposableEffect — liberación de recursos en Jetpack Compose

Autor: IT Sectr Publicado: 2026-06-30 Tiempo de lectura: 9 min

DisposableEffect es una función composable en Jetpack Compose diseñada para operaciones que requieren inicialización explícita y posterior liberación de recursos. A diferencia de otras APIs de side-effect, DisposableEffect proporciona un bloque onDispose que se ejecuta garantizadamente cuando el componente sale de la composición o cuando cambia la clave. Esto lo hace indispensable para trabajar con suscripciones nativas, listeners de sensores y recursos de hardware. Según Android Developers Documentation (2025), se recomienda usar DisposableEffect en todos los escenarios que requieran un par setup/teardown, análogo a onStart/onStop en el ciclo de vida de Activity.

Puntos clave

  • DisposableEffect — API de side-effect para configuración y liberación garantizada de recursos.
  • onDispose — bloque obligatorio que se ejecuta al salir de la composición o cambiar la clave.
  • Síncrono — a diferencia de LaunchedEffect, DisposableEffect funciona de forma síncrona sin corrutinas.
  • Limpieza — escenarios típicos: cancelar suscripción de LiveData, cerrar sockets, anular registro de BroadcastReceiver.
  • Claves — al cambiar la clave, se ejecuta onDispose para el valor anterior y se reinicializa con el nuevo.

Qué es DisposableEffect en Jetpack Compose

DisposableEffect es una herramienta clave para la gestión de recursos en Jetpack Compose. Su principal característica es la invocación garantizada del bloque onDispose cuando finaliza el ciclo de vida del componente composable. Este comportamiento es crítico para el desarrollo en Android, donde las suscripciones no cerradas a servicios del sistema pueden provocar fugas de memoria y cierres inesperados de la aplicación.

A diferencia de LaunchedEffect, que se ejecuta en un contexto asíncrono de corrutina, DisposableEffect se ejecuta de forma síncrona. Esto significa que no se pueden llamar funciones suspend dentro de él. La ejecución síncrona garantiza previsibilidad: puede estar seguro de que el código de inicialización se ejecuta antes del primer renderizado y el código de limpieza antes de que el componente se elimine de la memoria.

Según la Documentación de Jetpack Compose (2025), DisposableEffect debe usarse en cuatro escenarios principales: (1) suscripción a servicios del sistema (sensores, LocationManager), (2) registro de BroadcastReceiver, (3) trabajo con bibliotecas basadas en callbacks que no admiten corrutinas, (4) vinculación de componentes Compose con sistemas View heredados mediante 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")
}

Cómo funciona DisposableEffect con onDispose

La mecánica interna de DisposableEffect se basa en las fases del ciclo de vida de la composición. Cuando un componente composable entra en la composición, DisposableEffect ejecuta el bloque de código pasado. Este bloque devuelve un objeto DisposableEffectResult que contiene la lambda onDispose. La composición guarda este resultado y llama a onDispose cuando el componente sale de la composición, independientemente del motivo (navegación, cambio de estado del padre, eliminación de LazyColumn).

El mecanismo de claves en DisposableEffect funciona de manera similar a LaunchedEffect: cuando cambia cualquier clave, primero se ejecuta onDispose para el estado anterior, luego el bloque de inicialización se ejecuta nuevamente con las nuevas claves. Esto permite reconfigurar un recurso cuando cambian sus parámetros. Por ejemplo, si la clave es una URL de socket, cuando cambia, el socket anterior se cierra y se abre uno nuevo.

Importante: el bloque onDispose es un elemento obligatorio de DisposableEffect. Si no se llama a onDispose dentro del bloque, el código no se compila. Este requisito del compilador garantiza que el desarrollador no olvide prever la limpieza del recurso, lo que es una causa frecuente de errores en la gestión manual de suscripciones.

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 contra fugas de memoria

Las fugas de memoria en aplicaciones Android a menudo ocurren debido a listeners y suscripciones no registrados que continúan manteniendo una referencia a una Activity o Context después de que la pantalla se ha cerrado. DisposableEffect resuelve este problema a nivel de framework: si el desarrollador usa DisposableEffect para registrar un listener, onDispose garantizará cancelar la suscripción en cualquier escenario de finalización del componente.

Esto es especialmente crítico para LazyColumn y LazyGrid, donde los elementos se crean y destruyen constantemente a medida que el usuario se desplaza. Sin DisposableEffect, cada elemento que desaparece del área visible dejaría una suscripción activa. Con DisposableEffect, onDispose se llama para cada elemento descargado, garantizando que los recursos se liberen inmediatamente después de que el elemento salga de la pantalla.

Según Android Performance Patterns (Google, 2025), el uso de DisposableEffect para todas las suscripciones nativas reduce la cantidad de fugas de memoria en aplicaciones Compose en un 60–70% en comparación con la gestión manual mediante callbacks de ciclo de vida. El propio sistema rastrea el momento en que un componente sale de la composición y garantiza la ejecución de onDispose incluso durante el cierre de emergencia de la pantalla.

RecursoQué hace DisposableEffectSin DisposableEffect
BroadcastReceiverregister + onDispose → unregisterEl Receptor sigue activo
SensorManagerregisterListener + onDispose → unregisterListenerEl Sensor sigue enviando datos
Observable (no Flow)subscribe + onDispose → unsubscribeEl Callback mantiene una referencia
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackFuga de Callback
Socket / Channelopen + onDispose → closeLa Conexión permanece abierta

Suscripción a sensores mediante DisposableEffect

Uno de los ejemplos más ilustrativos del uso de DisposableEffect es el trabajo con sensores del dispositivo (acelerómetro, giroscopio, magnetómetro). Los sensores requieren una cancelación de registro obligatoria al finalizar, de lo contrario continúan consumiendo energía de la batería y enviando datos incluso después de cerrar la pantalla.

Ejemplo práctico: una aplicación para medir el ángulo de inclinación. DisposableEffect(Unit) registra un listener del acelerómetro cuando aparece el componente y lo anula en onDispose. Los datos del sensor se pasan al estado mediante mutableStateOf, lo que actualiza automáticamente la UI. Si la pantalla se desplaza en LazyColumn y el elemento desaparece, onDispose se activa de inmediato — el sensor deja de enviar datos para ese elemento.

Al cambiar el tipo de sensor (por ejemplo, de acelerómetro a giroscopio), la clave sensorType cambia, onDispose cancela la suscripción anterior y el nuevo bloque DisposableEffect registra el nuevo sensor. Sin claves, habría que verificar manualmente qué sensor estaba registrado anteriormente y llamar a unregisterListener con el listener correcto, lo que es propenso a errores.

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 mediante DisposableEffect

BroadcastReceiver es un ejemplo clásico de API que requiere un par obligatorio register / unregister. En una aplicación Compose, DisposableEffect es ideal para registrar un receptor durante la vida útil de una pantalla específica. Al entrar en la pantalla, se registra un BroadcastReceiver con el IntentFilter necesario; al salir, se cancela automáticamente en onDispose.

Un escenario típico es la monitorización del estado de la red. DisposableEffect registra un receptor para ConnectivityManager que notifica sobre cambios en la conectividad de red. Cuando cambia el estado (WiFi / datos móviles / sin red), el estado del composable se actualiza y la UI muestra el indicador correspondiente. Cuando la pantalla se cierra, onDispose garantiza la cancelación del registro, incluso si la aplicación pasa a segundo plano.

Para receptores con ContextCompat.registerReceiver y la bandera RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+), el uso de DisposableEffect se vuelve obligatorio porque el sistema requiere una especificación explícita del ámbito del receptor. DisposableEffect garantiza que el ámbito se limita al tiempo de vida de la pantalla, lo que coincide con los requisitos de seguridad de las versiones más recientes de 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) { ... }
}

Errores comunes con DisposableEffect

El primer error crítico es la falta de llamada a onDispose. El código dentro del bloque DisposableEffect debe llamar a onDispose, de lo contrario se produce un error de compilación. Sin embargo, los desarrolladores a veces intentan evitarlo colocando onDispose en una condición: if (condition) { onDispose { ... } }. Este código se compilará, pero onDispose no se registrará si la condición no se cumple — el recurso nunca se liberará.

El segundo error es usar DisposableEffect para operaciones asíncronas. Dado que DisposableEffect es síncrono, no se pueden escribir llamadas delay() o await() dentro de él. Si se necesita inicialización asíncrona con limpieza posterior, use una combinación de LaunchedEffect (para cargar datos) y DisposableEffect (para configurar/limpiar recursos nativos), o use un mecanismo separado con rememberCoroutineScope.

El tercer error es crear nuevos objetos dentro de DisposableEffect sin remember. Si los objetos (sensor, listener, receiver) se crean dentro del efecto en cada llamada y las claves cambian con frecuencia, esto provoca una creación excesiva de objetos y recolección de basura. Es mejor mover la creación de objetos a remember o remember { ... } fuera de DisposableEffect, y solo registrarlos y anularlos dentro del efecto.

Preguntas frecuentes

¿Cuál es la diferencia entre DisposableEffect y LaunchedEffect?

DisposableEffect funciona de forma síncrona y proporciona onDispose para la limpieza explícita de recursos. LaunchedEffect funciona de forma asíncrona en una corrutina y la cancela automáticamente al cambiar la clave o salir de la composición. Si un recurso requiere llamar a un método de limpieza (close, unregister, dispose) — use DisposableEffect. Si la operación es una función suspend — use LaunchedEffect.

¿Es obligatorio el bloque onDispose en DisposableEffect?

Sí, onDispose es obligatorio — el compilador Kotlin exige que se llame dentro del bloque DisposableEffect. Si no se llama a onDispose, el código no se compila. Esto se hizo intencionalmente para evitar que los desarrolladores olviden y garantizar que cada recurso abierto se cierre correctamente al salir de la composición.

¿Cómo manejar errores dentro de DisposableEffect?

Use try-catch dentro del bloque DisposableEffect. Si el registro del recurso puede lanzar una excepción (por ejemplo, sensor no encontrado), envuélvase en try y maneje el error en la UI a través de un estado separado. onDispose debe llamarse independientemente del éxito de la inicialización — colóquelo en un bloque finally o al final de la sección try.

¿Se puede usar DisposableEffect para suscribirse a Flow?

No se recomienda. Para Flow, es mejor usar LaunchedEffect con collectLatest o el método .collectAsState() con Lifecycle.repeatOnLifecycle. DisposableEffect no admite funciones suspend, por lo que suscribirse a Flow dentro de él requeriría lanzar una corrutina separada mediante CoroutineScope, lo que complica el código y aumenta el riesgo de fugas.

¿Cuántos DisposableEffect puede haber en un mismo composable?

No hay límites, pero se recomienda agrupar recursos relacionados en un solo DisposableEffect con varias operaciones dentro y un solo onDispose. Si los recursos son independientes (por ejemplo, sensor y BroadcastReceiver), es mejor dividirlos en DisposableEffects separados con diferentes claves — esto simplifica la depuración y evita la recreación no deseada de todos los recursos al cambiar una clave.

Resumen

  • DisposableEffect — API de side-effect de Jetpack Compose para inicialización síncrona con limpieza garantizada mediante onDispose.
  • onDispose — bloque obligatorio que se ejecuta al salir de la composición o cambiar la clave, previniendo fugas de memoria.
  • Claves — al cambiar una clave, primero se ejecuta onDispose para el valor anterior, luego se reinicializa con el nuevo.
  • Síncrono — DisposableEffect se ejecuta de forma síncrona; las funciones suspend no están disponibles dentro de él.
  • Escenarios típicos — BroadcastReceiver, sensores, listeners nativos, bibliotecas basadas en callbacks, integración con AndroidView.
  • Fugas — DisposableEffect reduce la cantidad de fugas en un 60–70% en comparación con la gestión manual mediante callbacks de ciclo de vida.
  • Errores — principales riesgos: llamada condicional a onDispose, uso para operaciones asíncronas, creación de objetos sin remember dentro del efecto.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también