DisposableEffect — ce este, eliberarea resurselor în Jetpack Compose

Autor: IT Sectr Publicat: 2026-06-30 Timp de citire: 9 min

DisposableEffect este o funcție composable în Jetpack Compose destinată operațiunilor care necesită inițializare explicită și curățarea ulterioară a resurselor. Spre deosebire de alte API-uri side-effect, DisposableEffect oferă blocul onDispose care se execută garantat la ieșirea componentului din compoziție sau la schimbarea cheii. Acest lucru îl face indispensabil pentru lucrul cu abonamente native, ascultători de senzori și resurse hardware. Conform Android Developers Documentation (2025), DisposableEffect este recomandat în toate scenariile care necesită o pereche setup/teardown, similară cu onStart/onStop în ciclul de viață al Activity.

Principalele puncte

  • DisposableEffect — API side-effect pentru configurarea și curățarea garantată a resurselor.
  • onDispose — bloc obligatoriu care se execută la ieșirea din compoziție sau la schimbarea cheii.
  • Sincronitate — spre deosebire de LaunchedEffect, DisposableEffect funcționează sincron fără corutine.
  • Curățare — scenarii tipice: dezabonarea de la LiveData, închiderea socketurilor, anularea înregistrării BroadcastReceiver.
  • Chei — la schimbarea cheii se execută onDispose pentru valoarea veche și reinițializarea cu cea nouă.

Ce este DisposableEffect în Jetpack Compose

DisposableEffect este un instrument cheie pentru gestionarea resurselor în Jetpack Compose. Caracteristica sa principală este apelul garantat al blocului onDispose la finalizarea ciclului de viață al componentului composable. Acest comportament este critic pentru dezvoltarea Android, unde abonamentele neînchise la serviciile de sistem pot duce la scurgeri de memorie și blocarea aplicației.

Spre deosebire de LaunchedEffect, care funcționează în context asincron al corutinei, DisposableEffect se execută sincron. Aceasta înseamnă că în interiorul său nu pot fi apelate funcții suspend. Sincronitatea asigură predictibilitatea: puteți fi sigur că codul de inițializare se va executa înainte de prima randare, iar codul de curățare — înainte ca componentul să fie eliminat din memorie.

Conform Documentației Jetpack Compose (2025), DisposableEffect trebuie utilizat în patru scenarii principale: (1) abonarea la servicii de sistem (senzori, LocationManager), (2) înregistrarea BroadcastReceiver, (3) lucrul cu biblioteci bazate pe callback care nu suportă corutine, (4) legarea componentelor Compose de sistemele Legacy View prin 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("Senzor: $value")
}

Cum funcționează DisposableEffect cu onDispose

Mecanismul intern al DisposableEffect se bazează pe fazele ciclului de viață al compoziției. Când componentul composable intră în compoziție, DisposableEffect apelează blocul de cod transmis. Acest bloc returnează un obiect DisposableEffectResult care conține lambda onDispose. Compoziția păstrează acest rezultat și apelează onDispose în momentul în care componentul părăsește compoziția — indiferent de motiv (navigare, schimbare de stare a părintelui, eliminare din LazyColumn).

Mecanismul cheilor în DisposableEffect funcționează similar cu LaunchedEffect: la schimbarea oricărei chei, mai întâi se execută onDispose pentru starea veche, apoi blocul de inițializare este repornit cu noile chei. Acest lucru permite reconfigurarea resursei la modificarea parametrilor săi. De exemplu, dacă cheia este URL-ul socketului, la schimbarea acestuia socketul vechi se închide și se deschide unul nou.

Important: blocul onDispose este un element obligatoriu al DisposableEffect. Dacă onDispose nu este apelat în interiorul blocului, codul nu se va compila. Această cerință a compilatorului garantează că dezvoltatorul nu va uita să prevadă curățarea resursei, ceea ce este o cauză frecventă de erori la gestionarea manuală a abonamentelor.

kotlin
// Utilizare corectă cu o cheie
DisposableEffect(sensorType) {
    val sensor = sensorManager.getDefaultSensor(sensorType)
    sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
    
    onDispose {
        sensorManager.unregisterListener(listener)
    }
}

// Resurse multiple într-un singur DisposableEffect
DisposableEffect(Unit) {
    context.registerReceiver(receiver, intentFilter)
    lifecycle.addObserver(observer)
    
    onDispose {
        context.unregisterReceiver(receiver)
        lifecycle.removeObserver(observer)
    }
}

DisposableEffect împotriva scurgerilor de memorie

Scurgerile de memorie în aplicațiile Android apar adesea din cauza ascultătorilor și abonamentelor neînregistrate care continuă să păstreze o referință la Activity sau Context după ce ecranul a fost închis. DisposableEffect rezolvă această problemă la nivel de framework: dacă dezvoltatorul a folosit DisposableEffect pentru înregistrarea unui ascultător, onDispose va anula garantat abonamentul în orice scenariu de finalizare a componentului.

Acest lucru este deosebit de critic pentru LazyColumn și LazyGrid, unde elementele sunt create și distruse constant pe măsură ce se derulează. Fără DisposableEffect, fiecare element care dispare din zona vizibilă ar lăsa un abonament activ. Cu DisposableEffect, onDispose este apelat pentru fiecare element descărcat, garantând că resursele sunt eliberate imediat după ce elementul părăsește ecranul.

Conform Android Performance Patterns (Google, 2025), utilizarea DisposableEffect pentru toate abonamentele native reduce numărul de scurgeri de memorie în aplicațiile Compose cu 60–70% comparativ cu gestionarea manuală prin callback-uri de ciclu de viață. Sistemul însuși urmărește momentul ieșirii din compoziție și garantează executarea onDispose chiar și la închiderea de urgență a ecranului.

ResursăCe face DisposableEffectFără DisposableEffect
BroadcastReceiverregister + onDispose → unregisterReceiver rămâne activ
SensorManagerregisterListener + onDispose → unregisterSenzorul continuă să trimită date
Observable (nu Flow)subscribe + onDispose → unsubscribeCallback păstrează referința
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackScurgere de callback
Socket / Channelopen + onDispose → closeConexiunea rămâne deschisă

Abonarea la senzori prin DisposableEffect

Unul dintre cele mai ilustrative exemple de utilizare a DisposableEffect — lucrul cu senzorii dispozitivului (accelerometru, giroscop, magnetometru). Senzorii necesită anularea obligatorie a înregistrării la finalizarea lucrului, altfel continuă să consume energie a bateriei și să trimită date chiar și după închiderea ecranului.

Exemplu practic: aplicație pentru măsurarea unghiului de înclinare. DisposableEffect(Unit) înregistrează un ascultător al accelerometrului la apariția componentului și anulează înregistrarea în onDispose. Datele senzorului sunt transmise în stare prin mutableStateOf, ceea ce actualizează automat UI. Dacă ecranul este derulat în LazyColumn și elementul dispare, onDispose se activează imediat — senzorul încetează să trimită date pentru acest element.

La schimbarea tipului de senzor (de exemplu, de la accelerometru la giroscop), cheia sensorType se schimbă, onDispose anulează vechiul abonament, iar noul bloc DisposableEffect înregistrează noul senzor. Fără chei, ar trebui să verificați manual care senzor a fost înregistrat anterior și să apelați unregisterListener cu listener-ul corect — ceea ce este predispus la erori.

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

Înregistrarea BroadcastReceiver prin DisposableEffect

BroadcastReceiver este un exemplu clasic de API care necesită perechea obligatorie register / unregister. În aplicația Compose, DisposableEffect este ideal pentru înregistrarea receptorului pe durata de viață a unui ecran specific. La intrarea pe ecran, BroadcastReceiver este înregistrat cu IntentFilter-ul necesar, la ieșire — anulat automat în onDispose.

Scenariu tipic — monitorizarea stării rețelei. DisposableEffect înregistrează un receptor pe ConnectivityManager care notifică despre modificările conexiunii de rețea. La schimbarea stării (WiFi / date mobile / fără rețea), starea composable este actualizată, iar UI afișează indicatorul corespunzător. Când ecranul este închis, onDispose anulează garantat înregistrarea — chiar dacă aplicația trece în fundal.

Pentru receptoarele cu ContextCompat.registerReceiver și flag-ul RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) utilizarea DisposableEffect devine obligatorie, deoarece sistemul necesită specificarea explicită a domeniului de acțiune al receptorului. DisposableEffect garantează că domeniul de acțiune este limitat la durata de viață a ecranului, ceea ce corespunde cerințelor de securitate ale noilor versiuni 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) { ... }
}

Erori tipice cu DisposableEffect

Prima eroare critică — lipsa apelului onDispose. Codul din interiorul blocului DisposableEffect trebuie să apeleze onDispose, altfel va apărea o eroare de compilare. Cu toate acestea, dezvoltatorii uneori încearcă să evite acest lucru plasând onDispose într-o condiție: if (condition) { onDispose { ... } }. Un astfel de cod se va compila, dar onDispose nu va fi înregistrat dacă condiția nu este îndeplinită — resursa nu va fi niciodată eliberată.

A doua eroare — utilizarea DisposableEffect pentru operații asincrone. Deoarece DisposableEffect este sincron, în interiorul său nu pot fi scrise apeluri delay() sau await(). Dacă este necesară o inițializare asincronă cu curățare ulterioară, utilizați combinația LaunchedEffect (pentru încărcarea datelor) și DisposableEffect (pentru configurarea/curățarea resurselor native) sau un mecanism separat cu rememberCoroutineScope.

A treia eroare — crearea de obiecte noi în interiorul DisposableEffect fără remember. Dacă în interiorul efectului sunt create obiecte (senzor, listener, receptor) la fiecare apel, iar cheile se schimbă frecvent, aceasta duce la crearea excesivă de obiecte și colectare de gunoi. Mai bine să mutați crearea obiectelor în remember sau remember { ... } în afara DisposableEffect, iar în interiorul efectului doar să înregistrați și să anulați.

Întrebări frecvente

Care este diferența dintre DisposableEffect și LaunchedEffect?

DisposableEffect funcționează sincron și oferă onDispose pentru curățarea explicită a resurselor. LaunchedEffect funcționează asincron într-o corutină și o anulează automat la schimbarea cheii sau la ieșirea din compoziție. Dacă resursa necesită apelarea unei metode cleanup (close, unregister, dispose) — utilizați DisposableEffect. Dacă operația este o funcție suspend — utilizați LaunchedEffect.

Blocul onDispose în DisposableEffect este obligatoriu?

Da, onDispose este obligatoriu — compilatorul Kotlin cere apelarea sa în interiorul blocului DisposableEffect. Dacă onDispose nu este apelat, codul nu se va compila. Acest lucru a fost făcut intenționat pentru a preveni uitarea dezvoltatorilor și a garanta că fiecare resursă deschisă va fi închisă la ieșirea din compoziție.

Cum gestionăm erorile în interiorul DisposableEffect?

Utilizați try-catch în interiorul blocului DisposableEffect. Dacă înregistrarea resursei poate arunca o excepție (de exemplu, senzorul nu a fost găsit), înfășurați-o în try și gestionați eroarea în UI printr-o stare separată. onDispose trebuie apelat indiferent de succesul inițializării — plasați-l în blocul finally sau la sfârșitul secțiunii try.

Putem folosi DisposableEffect pentru abonarea la Flow?

Nu este recomandat. Pentru Flow este mai bine să utilizați LaunchedEffect cu collectLatest sau metoda .collectAsState() cu Lifecycle.repeatOnLifecycle. DisposableEffect nu suportă funcții suspend, deci abonarea la Flow în interiorul său ar necesita lansarea unei corutine separate prin CoroutineScope, ceea ce complică codul și crește riscul de scurgeri.

Câte DisposableEffect pot fi într-un singur composable?

Nu există limitări, dar se recomandă gruparea resurselor conexe într-un singur DisposableEffect cu mai multe operații în interior și un singur onDispose. Dacă resursele sunt independente (de exemplu, senzor și BroadcastReceiver), este mai bine să le separați în DisposableEffect-uri separate cu chei diferite — aceasta simplifică depanarea și previne recrearea nedorită a tuturor resurselor la schimbarea unei singure chei.

Concluzii

  • DisposableEffect — API side-effect Jetpack Compose pentru inițializare sincronă cu curățare garantată prin onDispose.
  • onDispose — bloc obligatoriu care se execută la ieșirea din compoziție sau la schimbarea cheii, prevenind scurgerile de memorie.
  • Chei — la schimbarea cheii se execută mai întâi onDispose pentru valoarea veche, apoi reinițializarea cu cea nouă.
  • Sincronitate — DisposableEffect se execută sincron, funcțiile suspend nu sunt disponibile în interiorul său.
  • Scenarii tipice — BroadcastReceiver, senzori, ascultători nativi, biblioteci bazate pe callback, integrare AndroidView.
  • Scurgeri — DisposableEffect reduce numărul de scurgeri cu 60–70% comparativ cu gestionarea manuală a callback-urilor de ciclu de viață.
  • Erori — riscuri principale: apel condiționat al onDispose, utilizare pentru operații async, crearea de obiecte fără remember în interiorul efectului.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și