DisposableEffect — co to je, uvolňování zdrojů v Jetpack Compose

Autor: IT Sectr Publikováno: 2026-06-30 Doba čtení: 9 min

DisposableEffect je composable-funkce v Jetpack Compose určená pro operace vyžadující explicitní inicializaci a následné čištění zdrojů. Na rozdíl od jiných side-effect API poskytuje DisposableEffect blok onDispose, který se zaručeně provede při opuštění kompozice komponentou nebo při změně klíče. To jej činí nepostradatelným pro práci s nativními předplatnými, posluchači senzorů a hardwarovými zdroji. Podle Android Developers Documentation (2025) se DisposableEffect doporučuje ve všech scénářích vyžadujících pár setup/teardown, analogicky k onStart/onStop v životním cyklu Activity.

Hlavní body

  • DisposableEffect — side-effect API pro nastavení a zaručené čištění zdrojů.
  • onDispose — povinný blok, který se provádí při opuštění kompozice nebo změně klíče.
  • Synchronnost — na rozdíl od LaunchedEffect pracuje DisposableEffect synchronně bez korutin.
  • Čištění — typické scénáře: odhlášení z LiveData, zavírání socketů, zrušení registrace BroadcastReceiver.
  • Klíče — při změně klíče se provede onDispose pro starou hodnotu a opětovná inicializace s novou.

Co je DisposableEffect v Jetpack Compose

DisposableEffect je klíčový nástroj pro správu zdrojů v Jetpack Compose. Jeho hlavní vlastností je zaručené volání bloku onDispose při ukončení životního cyklu composable-komponenty. Toto chování je kritické pro vývoj Android, kde neuzavřená předplatná systémových služeb mohou vést k únikům paměti a pádům aplikace.

Na rozdíl od LaunchedEffect, který pracuje v asynchronním kontextu korutiny, se DisposableEffect provádí synchronně. To znamená, že uvnitř něj nelze volat suspend-funkce. Synchronnost zajišťuje předvídatelnost: můžete si být jisti, že inicializační kód se provede před prvním vykreslením a čisticí kód — před odstraněním komponenty z paměti.

Podle Dokumentace Jetpack Compose (2025) by se DisposableEffect měl používat ve čtyřech hlavních scénářích: (1) předplatné systémových služeb (senzory, LocationManager), (2) registrace BroadcastReceiver, (3) práce s callbackovými knihovnami, které nepodporují korutiny, (4) propojení Compose komponent s Legacy View systémy přes 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")
}

Jak DisposableEffect funguje s onDispose

Vnitřní mechanika DisposableEffect je založena na fázích životního cyklu kompozice. Když composable-komponenta vstoupí do kompozice, DisposableEffect zavolá předaný blok kódu. Tento blok vrátí objekt DisposableEffectResult obsahující lambdu onDispose. Kompozice tento výsledek uloží a zavolá onDispose v okamžiku, kdy komponenta opouští kompozici — bez ohledu na důvod (navigace, změna stavu rodiče, odstranění z LazyColumn).

Mechanismus klíčů v DisposableEffect funguje analogicky jako LaunchedEffect: při změně libovolného klíče se nejprve provede onDispose pro starý stav, poté se inicializační blok znovu spustí s novými klíči. To umožňuje překonfigurovat zdroj při změně jeho parametrů. Pokud je například klíčem URL socketu, při jeho změně se starý socket zavře a otevře se nový.

Důležité: blok onDispose je povinným prvkem DisposableEffect. Pokud se onDispose nevyvolá uvnitř bloku, kód se nezkompiluje. Tento požadavek kompilátoru zaručuje, že vývojář nezapomene na čištění zdroje, což je častou příčinou chyb při ruční správě předplatných.

kotlin
// Správné použití s klíčem
DisposableEffect(sensorType) {
    val sensor = sensorManager.getDefaultSensor(sensorType)
    sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
    
    onDispose {
        sensorManager.unregisterListener(listener)
    }
}

// Více zdrojů v jednom DisposableEffect
DisposableEffect(Unit) {
    context.registerReceiver(receiver, intentFilter)
    lifecycle.addObserver(observer)
    
    onDispose {
        context.unregisterReceiver(receiver)
        lifecycle.removeObserver(observer)
    }
}

DisposableEffect proti únikům paměti

Úniky paměti v Android aplikacích často vznikají kvůli neregistrovaným posluchačům a předplatným, které nadále drží odkaz na Activity nebo Context po zavření obrazovky. DisposableEffect řeší tento problém na úrovni frameworku: pokud vývojář použil DisposableEffect k registraci posluchače, onDispose zaručeně zruší předplatné v jakémkoli scénáři ukončení komponenty.

To je obzvláště kritické pro LazyColumn a LazyGrid, kde jsou prvky neustále vytvářeny a ničeny při rolování. Bez DisposableEffect by každý prvek mizející z viditelné oblasti zanechal aktivní předplatné. S DisposableEffect se onDispose volá pro každý uvolněný prvek, což zaručuje okamžité uvolnění zdrojů, jakmile prvek opustí obrazovku.

Podle Android Performance Patterns (Google, 2025) použití DisposableEffect pro všechna nativní předplatná snižuje počet úniků paměti v Compose aplikacích o 60–70% ve srovnání s ruční správou pomocí lifecycle callbacků. Systém sám sleduje okamžik opuštění kompozice a zaručuje provedení onDispose i při nouzovém zavření obrazovky.

ZdrojCo dělá DisposableEffectBez DisposableEffect
BroadcastReceiverregister + onDispose → unregisterReceiver zůstává aktivní
SensorManagerregisterListener + onDispose → unregisterSenzor nadále posílá data
Observable (ne Flow)subscribe + onDispose → unsubscribeCallback drží odkaz
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackÚnik callbacku
Socket / Channelopen + onDispose → closeSpojení zůstává otevřené

Předplatné senzorů přes DisposableEffect

Jeden z nejnázornějších příkladů použití DisposableEffect — práce se senzory zařízení (akcelerometr, gyroskop, magnetometr). Senzory vyžadují povinné zrušení registrace při ukončení práce, jinak nadále spotřebovávají energii baterie a odesílají data i po zavření obrazovky.

Praktický příklad: aplikace pro měření úhlu sklonu. DisposableEffect(Unit) registruje posluchač akcelerometru při objevení komponenty a ruší registraci v onDispose. Data senzoru jsou předávána do stavu přes mutableStateOf, což automaticky aktualizuje UI. Pokud je obrazovka rolována v LazyColumn a prvek zmizí, onDispose se aktivuje okamžitě — senzor přestane odesílat data pro tento prvek.

Při změně typu senzoru (například z akcelerometru na gyroskop) se klíč sensorType změní, onDispose zruší staré předplatné a nový blok DisposableEffect zaregistruje nový senzor. Bez klíčů byste museli ručně kontrolovat, který senzor byl dříve registrován, a volat unregisterListener se správným listenerem — což je náchylné k chybám.

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

Registrace BroadcastReceiver přes DisposableEffect

BroadcastReceiver je klasickým příkladem API vyžadujícím povinný pár register / unregister. V Compose aplikaci je DisposableEffect ideální pro registraci přijímače po dobu životnosti konkrétní obrazovky. Při vstupu na obrazovku je BroadcastReceiver registrován s příslušným IntentFilter, při výstupu — automaticky zrušen v onDispose.

Typický scénář — monitorování stavu sítě. DisposableEffect registruje přijímač na ConnectivityManager, který informuje o změnách síťového připojení. Při změně stavu (WiFi / mobilní data / bez sítě) se aktualizuje stav composable a UI zobrazí odpovídající indikátor. Když je obrazovka zavřena, onDispose zaručeně ruší registraci — i když aplikace přejde na pozadí.

Pro přijímače s ContextCompat.registerReceiver a příznakem RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) se použití DisposableEffect stává povinným, protože systém vyžaduje explicitní určení domény působnosti přijímače. DisposableEffect zaručuje, že doména působnosti je omezena na dobu života obrazovky, což je v souladu s bezpečnostními požadavky nových verzí 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) { ... }
}

Typické chyby s DisposableEffect

První kritická chyba — chybějící volání onDispose. Kód uvnitř bloku DisposableEffect musí volat onDispose, jinak dojde k chybě kompilace. Vývojáři se však někdy pokoušejí to obejít umístěním onDispose do podmínky: if (condition) { onDispose { ... } }. Takový kód se zkompiluje, ale pokud podmínka není splněna, onDispose není registrován — zdroj není nikdy uvolněn.

Druhá chyba — použití DisposableEffect pro asynchronní operace. Protože je DisposableEffect synchronní, nelze do něj psát volání delay() nebo await(). Pokud je potřeba asynchronní inicializace s následným čištěním, použijte kombinaci LaunchedEffect (pro načtení dat) a DisposableEffect (pro nastavení/čištění nativních zdrojů) nebo samostatný mechanismus s rememberCoroutineScope.

Třetí chyba — vytváření nových objektů uvnitř DisposableEffect bez remember. Pokud se uvnitř efektu při každém volání vytvářejí objekty (senzor, listener, přijímač) a klíče se často mění, vede to k nadměrnému vytváření objektů a garbage collection. Lepší je přesunout vytváření objektů do remember nebo remember { ... } mimo DisposableEffect a uvnitř efektu pouze registrovat a rušit.

Často kladené otázky

Jaký je rozdíl mezi DisposableEffect a LaunchedEffect?

DisposableEffect pracuje synchronně a poskytuje onDispose pro explicitní čištění zdrojů. LaunchedEffect pracuje asynchronně v korutině a automaticky ji ruší při změně klíče nebo opuštění kompozice. Pokud zdroj vyžaduje volání cleanup metody (close, unregister, dispose) — použijte DisposableEffect. Pokud je operace suspend-funkcí — použijte LaunchedEffect.

Je blok onDispose v DisposableEffect povinný?

Ano, onDispose je povinný — kompilátor Kotlin vyžaduje jeho volání uvnitř bloku DisposableEffect. Pokud se onDispose nevyvolá, kód se nezkompiluje. Toto je záměrně navrženo, aby se předešlo zapomnětlivosti vývojářů a zajistilo se, že každý otevřený zdroj bude uzavřen při opuštění kompozice.

Jak zpracovat chyby uvnitř DisposableEffect?

Použijte try-catch uvnitř bloku DisposableEffect. Pokud registrace zdroje může vyvolat výjimku (například senzor nebyl nalezen), obalte ji do try a zpracujte chybu v UI přes samostatný stav. onDispose musí být volán bez ohledu na úspěšnost inicializace — umístěte jej do bloku finally nebo na konec try sekce.

Lze použít DisposableEffect pro předplatné Flow?

Nedoporučuje se. Pro Flow je lepší použít LaunchedEffect s collectLatest nebo metodu .collectAsState() s Lifecycle.repeatOnLifecycle. DisposableEffect nepodporuje suspend-funkce, takže předplatné Flow uvnitř by vyžadovalo spuštění samostatné korutiny přes CoroutineScope, což komplikuje kód a zvyšuje riziko úniků.

Kolik DisposableEffect může být v jednom composable?

Neexistují omezení, ale doporučuje se seskupovat související zdroje do jednoho DisposableEffect s více operacemi uvnitř a jedním onDispose. Pokud jsou zdroje nezávislé (např. senzor a BroadcastReceiver), je lepší je rozdělit do samostatných DisposableEffect s různými klíči — to zjednodušuje ladění a zabraňuje nežádoucímu opětovnému vytváření všech zdrojů při změně jednoho klíče.

Shrnutí

  • DisposableEffect — side-effect API Jetpack Compose pro synchronní inicializaci se zaručeným čištěním přes onDispose.
  • onDispose — povinný blok provádějící se při opuštění kompozice nebo změně klíče, zabraňující únikům paměti.
  • Klíče — při změně klíče se nejprve provede onDispose pro starou hodnotu, poté opětovná inicializace s novou.
  • Synchronnost — DisposableEffect se provádí synchronně, suspend-funkce v něm nejsou dostupné.
  • Typické scénáře — BroadcastReceiver, senzory, nativní posluchači, callbackové knihovny, AndroidView integrace.
  • Úniky — DisposableEffect snižuje počet úniků o 60–70% ve srovnání s ruční správou lifecycle callbacků.
  • Chyby — hlavní rizika: podmíněné volání onDispose, použití pro async operace, vytváření objektů bez remember uvnitř efektu.

Vyvineme mobilní aplikaci na klíč

IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.

Prodiskutovat projekt

Přečtěte si také