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 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.
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")
}
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.
// 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)
}
}
Ú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.
| Zdroj | Co dělá DisposableEffect | Bez DisposableEffect |
|---|---|---|
| BroadcastReceiver | register + onDispose → unregister | Receiver zůstává aktivní |
| SensorManager | registerListener + onDispose → unregister | Senzor nadále posílá data |
| Observable (ne Flow) | subscribe + onDispose → unsubscribe | Callback drží odkaz |
| TextureView / SurfaceView | setCallback + onDispose → removeCallback | Únik callbacku |
| Socket / Channel | open + onDispose → close | Spojení zůstává otevřené |
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.
@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")
}
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.
@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) { ... }
}
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
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.
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.
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.
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ů.
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í
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í.
Přečtěte si také