DisposableEffect — liberazione delle risorse in Jetpack Compose

Autore: IT Sectr Pubblicato: 2026-06-30 Tempo di lettura: 9 min

DisposableEffect è una funzione composable in Jetpack Compose progettata per operazioni che richiedono inizializzazione esplicita e successiva pulizia delle risorse. A differenza di altre API side-effect, DisposableEffect fornisce un blocco onDispose che viene garantito essere eseguito quando il componente esce dalla composizione o quando la chiave cambia. Questo lo rende indispensabile per lavorare con sottoscrizioni native, listener di sensori e risorse hardware. Secondo Android Developers Documentation (2025), DisposableEffect è raccomandato in tutti gli scenari che richiedono una coppia setup/teardown, analoga a onStart/onStop nel ciclo di vita dell’Activity.

Punti chiave

  • DisposableEffect — API side-effect per configurazione e pulizia garantita delle risorse.
  • onDispose — blocco obbligatorio eseguito all’uscita dalla composizione o al cambio della chiave.
  • Sincrono — a differenza di LaunchedEffect, DisposableEffect funziona in modo sincrono senza coroutine.
  • Pulizia — scenari tipici: cancellare sottoscrizione a LiveData, chiudere socket, annullare registrazione di BroadcastReceiver.
  • Chiavi — quando la chiave cambia, onDispose viene eseguito per il vecchio valore e si verifica una reinizializzazione con il nuovo.

Cos’è DisposableEffect in Jetpack Compose

DisposableEffect è uno strumento chiave per la gestione delle risorse in Jetpack Compose. La sua caratteristica principale è l’invocazione garantita del blocco onDispose quando il ciclo di vita del componente composable termina. Questo comportamento è critico per lo sviluppo Android, dove sottoscrizioni non chiuse ai servizi di sistema possono portare a perdite di memoria e crash dell’applicazione.

A differenza di LaunchedEffect, che funziona in un contesto asincrono di coroutine, DisposableEffect si esegue in modo sincrono. Ciò significa che non puoi chiamare funzioni suspend al suo interno. L’esecuzione sincrona garantisce prevedibilità: puoi essere certo che il codice di inizializzazione venga eseguito prima del primo rendering e il codice di pulizia prima che il componente venga rimosso dalla memoria.

Secondo la Documentazione Jetpack Compose (2025), DisposableEffect dovrebbe essere utilizzato in quattro scenari principali: (1) sottoscrizione ai servizi di sistema (sensori, LocationManager), (2) registrazione di BroadcastReceiver, (3) lavoro con librerie basate su callback che non supportano coroutine, (4) collegamento di componenti Compose a sistemi View legacy tramite 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("Sensore: $value")
}

Come funziona DisposableEffect con onDispose

La meccanica interna di DisposableEffect si basa sulle fasi del ciclo di vita della composizione. Quando un componente composable entra nella composizione, DisposableEffect esegue il blocco di codice passato. Questo blocco restituisce un oggetto DisposableEffectResult contenente la lambda onDispose. La composizione salva questo risultato e chiama onDispose quando il componente esce dalla composizione — indipendentemente dal motivo (navigazione, cambiamento dello stato genitore, rimozione da LazyColumn).

Il meccanismo delle chiavi in DisposableEffect funziona in modo simile a LaunchedEffect: quando una chiave cambia, onDispose viene prima eseguito per il vecchio stato, poi il blocco di inizializzazione viene eseguito di nuovo con le nuove chiavi. Ciò consente di riconfigurare una risorsa quando i suoi parametri cambiano. Ad esempio, se la chiave è un URL di socket, quando cambia, il vecchio socket viene chiuso e ne viene aperto uno nuovo.

Importante: il blocco onDispose è un elemento obbligatorio di DisposableEffect. Se non chiami onDispose all’interno del blocco, il codice non verrà compilato. Questo requisito del compilatore garantisce che lo sviluppatore non dimentichi di prevedere la pulizia della risorsa, che è una causa comune di errori nella gestione manuale delle sottoscrizioni.

kotlin
// Utilizzo corretto con una chiave
DisposableEffect(sensorType) {
    val sensor = sensorManager.getDefaultSensor(sensorType)
    sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
    
    onDispose {
        sensorManager.unregisterListener(listener)
    }
}

// Risorse multiple in un solo DisposableEffect
DisposableEffect(Unit) {
    context.registerReceiver(receiver, intentFilter)
    lifecycle.addObserver(observer)
    
    onDispose {
        context.unregisterReceiver(receiver)
        lifecycle.removeObserver(observer)
    }
}

DisposableEffect contro le perdite di memoria

Le perdite di memoria nelle applicazioni Android spesso si verificano a causa di listener e sottoscrizioni non registrati che continuano a mantenere un riferimento a un’Activity o Context dopo che lo schermo è stato chiuso. DisposableEffect risolve questo problema a livello di framework: se lo sviluppatore usa DisposableEffect per registrare un listener, onDispose garantirà di annullare la sottoscrizione in qualsiasi scenario di terminazione del componente.

Ciò è particolarmente critico per LazyColumn e LazyGrid, dove gli elementi vengono costantemente creati e distrutti mentre l’utente scorre. Senza DisposableEffect, ogni elemento che scompare dall’area visibile lascerebbe una sottoscrizione attiva. Con DisposableEffect, onDispose viene chiamato per ogni elemento scaricato, garantendo che le risorse vengano liberate immediatamente dopo che l’elemento lascia lo schermo.

Secondo Android Performance Patterns (Google, 2025), l’uso di DisposableEffect per tutte le sottoscrizioni native riduce il numero di perdite di memoria nelle applicazioni Compose del 60–70% rispetto alla gestione manuale tramite callback del ciclo di vita. Il sistema stesso traccia il momento in cui il componente esce dalla composizione e garantisce l’esecuzione di onDispose anche durante la chiusura emergenziale dello schermo.

RisorsaCosa fa DisposableEffectSenza DisposableEffect
BroadcastReceiverregister + onDispose → unregisterIl ricevitore rimane attivo
SensorManagerregisterListener + onDispose → unregisterListenerIl sensore continua a inviare dati
Observable (non Flow)subscribe + onDispose → unsubscribeIl callback mantiene un riferimento
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackPerdita di callback
Socket / Channelopen + onDispose → closeLa connessione rimane aperta

Sottoscrizione ai sensori tramite DisposableEffect

Uno degli esempi più illustrativi dell’uso di DisposableEffect è il lavoro con i sensori del dispositivo (accelerometro, giroscopio, magnetometro). I sensori richiedono un annullamento obbligatorio della registrazione al termine, altrimenti continuano a consumare energia della batteria e a inviare dati anche dopo la chiusura dello schermo.

Esempio pratico: un’applicazione per la misurazione dell’angolo di inclinazione. DisposableEffect(Unit) registra un listener dell’accelerometro quando il componente appare e annulla la registrazione in onDispose. I dati del sensore vengono passati allo stato tramite mutableStateOf, che aggiorna automaticamente l’interfaccia. Se lo schermo scorre in LazyColumn e l’elemento scompare, onDispose si attiva immediatamente — il sensore smette di inviare dati per quell’elemento.

Quando si cambia il tipo di sensore (ad esempio, da accelerometro a giroscopio), la chiave sensorType cambia, onDispose annulla la vecchia sottoscrizione e il nuovo blocco DisposableEffect registra il nuovo sensore. Senza chiavi, dovresti verificare manualmente quale sensore era stato registrato in precedenza e chiamare unregisterListener con il listener corretto — il che è soggetto a errori.

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("Valore: $sensorValue")
}

Registrazione di BroadcastReceiver tramite DisposableEffect

BroadcastReceiver è un classico esempio di API che richiede una coppia obbligatoria register / unregister. In un’applicazione Compose, DisposableEffect è ideale per registrare un ricevitore per la durata di vita di uno schermo specifico. All’ingresso dello schermo, viene registrato un BroadcastReceiver con l’IntentFilter necessario; all’uscita, viene automaticamente annullato in onDispose.

Uno scenario tipico è il monitoraggio dello stato della rete. DisposableEffect registra un ricevitore per ConnectivityManager che notifica i cambiamenti di connettività di rete. Quando lo stato cambia (WiFi / dati mobili / nessuna rete), lo stato del composable viene aggiornato e l’interfaccia mostra l’indicatore corrispondente. Quando lo schermo si chiude, onDispose garantisce l’annullamento della registrazione — anche se l’app va in background.

Per i ricevitori che utilizzano ContextCompat.registerReceiver con il flag RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+), l’uso di DisposableEffect diventa obbligatorio perché il sistema richiede una specifica esplicita dell’ambito del ricevitore. DisposableEffect garantisce che l’ambito sia limitato alla durata di vita dello schermo, il che è in linea con i requisiti di sicurezza delle versioni più recenti di 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) { ... }
}

Errori comuni con DisposableEffect

Il primo errore critico è l’assenza della chiamata a onDispose. Il codice all’interno del blocco DisposableEffect deve chiamare onDispose, altrimenti si verifica un errore di compilazione. Tuttavia, gli sviluppatori a volte cercano di aggirare questo problema inserendo onDispose in una condizione: if (condition) { onDispose { ... } }. Questo codice verrà compilato, ma onDispose non verrà registrato se la condizione non è soddisfatta — la risorsa non verrà mai liberata.

Il secondo errore è usare DisposableEffect per operazioni asincrone. Poiché DisposableEffect è sincrono, non puoi scrivere chiamate delay() o await() al suo interno. Se hai bisogno di inizializzazione asincrona con successiva pulizia, usa una combinazione di LaunchedEffect (per il caricamento dei dati) e DisposableEffect (per configurare/pulire le risorse native), o usa un meccanismo separato con rememberCoroutineScope.

Il terzo errore è creare nuovi oggetti all’interno di DisposableEffect senza remember. Se gli oggetti (sensore, listener, ricevitore) vengono creati all’interno dell’effetto a ogni chiamata e le chiavi cambiano frequentemente, ciò porta a una creazione eccessiva di oggetti e alla garbage collection. È meglio spostare la creazione degli oggetti in remember o remember { ... } all’esterno di DisposableEffect, e solo registrarli e annullarli all’interno dell’effetto.

Domande frequenti

Qual è la differenza tra DisposableEffect e LaunchedEffect?

DisposableEffect funziona in modo sincrono e fornisce onDispose per la pulizia esplicita delle risorse. LaunchedEffect funziona in modo asincrono in una coroutine e la annulla automaticamente al cambio della chiave o all’uscita dalla composizione. Se una risorsa richiede la chiamata di un metodo di pulizia (close, unregister, dispose) — usa DisposableEffect. Se l’operazione è una funzione suspend — usa LaunchedEffect.

Il blocco onDispose è obbligatorio in DisposableEffect?

Sì, onDispose è obbligatorio — il compilatore Kotlin richiede che venga chiamato all’interno del blocco DisposableEffect. Se non chiami onDispose, il codice non verrà compilato. È stato fatto intenzionalmente per evitare che gli sviluppatori dimentichino e garantire che ogni risorsa aperta venga correttamente chiusa all’uscita dalla composizione.

Come gestire gli errori all’interno di DisposableEffect?

Usa try-catch all’interno del blocco DisposableEffect. Se la registrazione della risorsa può generare un’eccezione (ad esempio, sensore non trovato), avvolgila in try e gestisci l’errore nell’interfaccia tramite uno stato separato. onDispose dovrebbe essere chiamato indipendentemente dal successo dell’inizializzazione — mettilo in un blocco finally o alla fine della sezione try.

Posso usare DisposableEffect per sottoscrivermi a Flow?

Non raccomandato. Per Flow, è meglio usare LaunchedEffect con collectLatest o il metodo .collectAsState() con Lifecycle.repeatOnLifecycle. DisposableEffect non supporta le funzioni suspend, quindi sottoscriversi a Flow al suo interno richiederebbe l’avvio di una coroutine separata tramite CoroutineScope, complicando il codice e aumentando il rischio di perdite.

Quanti DisposableEffect possono esserci in un composable?

Non ci sono limiti, ma si raccomanda di raggruppare le risorse correlate in un unico DisposableEffect con più operazioni all’interno e un solo onDispose. Se le risorse sono indipendenti (ad esempio, sensore e BroadcastReceiver), è meglio dividerle in DisposableEffect separati con chiavi diverse — questo semplifica il debug e previene la ricreazione indesiderata di tutte le risorse quando una chiave cambia.

Riepilogo

  • DisposableEffect — API side-effect di Jetpack Compose per inizializzazione sincrona con pulizia garantita tramite onDispose.
  • onDispose — blocco obbligatorio eseguito all’uscita dalla composizione o al cambio della chiave, prevenendo perdite di memoria.
  • Chiavi — quando una chiave cambia, onDispose viene prima eseguito per il vecchio valore, poi la reinizializzazione avviene con il nuovo.
  • Sincrono — DisposableEffect si esegue in modo sincrono; le funzioni suspend non sono disponibili al suo interno.
  • Scenari tipici — BroadcastReceiver, sensori, listener nativi, librerie basate su callback, integrazione AndroidView.
  • Perdite — DisposableEffect riduce il numero di perdite del 60–70% rispetto alla gestione manuale tramite callback del ciclo di vita.
  • Errori — principali rischi: chiamata condizionale di onDispose, uso per operazioni asincrone, creazione di oggetti senza remember all’interno dell’effetto.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche