DisposableEffect är en composable-funktion i Jetpack Compose avsedd för operationer som kräver explicit initiering och efterföljande rensning av resurser. Till skillnad från andra side-effect-API:er tillhandahåller DisposableEffect ett onDispose-block som garanterat körs när komponenten lämnar kompositionen eller när nyckeln ändras. Detta gör den oumbärlig för att arbeta med native-prenumerationer, sensor-lyssnare och hårdvaruresurser. Enligt Android Developers Documentation (2025) rekommenderas DisposableEffect i alla scenarier som kräver ett setup/teardown-par, analogt med onStart/onStop i Activitys livscykel.
Huvudpunkter
DisposableEffect är ett nyckelverktyg för resurshantering i Jetpack Compose. Dess huvudsakliga egenskap är det garanterade anropet av onDispose-blocket vid slutet av composable-komponentens livscykel. Detta beteende är kritiskt för Android-utveckling, där oavslutade prenumerationer på systemtjänster kan leda till minnesläckor och krascher av applikationen.
Till skillnad från LaunchedEffect, som fungerar i en asynkron korutinkontext, körs DisposableEffect synkront. Detta innebär att suspend-funktioner inte kan anropas inuti det. Synkronicitet säkerställer förutsägbarhet: du kan vara säker på att initieringskoden körs före den första renderingen och rensningskoden — innan komponenten tas bort från minnet.
Enligt Jetpack Compose-dokumentationen (2025) bör DisposableEffect användas i fyra huvudsakliga scenarier: (1) prenumeration på systemtjänster (sensorer, LocationManager), (2) registrering av BroadcastReceiver, (3) arbete med callback-baserade bibliotek som inte stöder korutiner, (4) koppling av Compose-komponenter till Legacy View-system via 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("Sensor: $value")
}
Den interna mekaniken för DisposableEffect är baserad på faserna i kompositionens livscykel. När en composable-komponent går in i kompositionen anropar DisposableEffect det överförda kodblocket. Detta block returnerar ett DisposableEffectResult-objekt som innehåller onDispose-lambdan. Kompositionen sparar detta resultat och anropar onDispose i det ögonblick komponenten lämnar kompositionen — oavsett orsak (navigering, förändring av förälderns tillstånd, borttagning från LazyColumn).
Nyckelmekanismen i DisposableEffect fungerar analogt med LaunchedEffect: när någon nyckel ändras körs först onDispose för det gamla tillståndet, sedan startas initieringsblocket om med de nya nycklarna. Detta gör det möjligt att omkonfigurera resursen när dess parametrar ändras. Om nyckeln till exempel är socketens URL, stängs den gamla socketen och en ny öppnas när den ändras.
Viktigt: onDispose-blocket är en obligatorisk del av DisposableEffect. Om onDispose inte anropas inuti blocket kommer koden inte att kompileras. Detta kompilatorkrav säkerställer att utvecklaren inte glömmer att förbereda rensning av resursen, vilket är en vanlig orsak till fel vid manuell hantering av prenumerationer.
// Korrekt användning med en nyckel
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
// Flera resurser i en DisposableEffect
DisposableEffect(Unit) {
context.registerReceiver(receiver, intentFilter)
lifecycle.addObserver(observer)
onDispose {
context.unregisterReceiver(receiver)
lifecycle.removeObserver(observer)
}
}
Minnesläckor i Android-applikationer uppstår ofta på grund av oregistrerade lyssnare och prenumerationer som fortsätter att hålla en referens till Activity eller Context efter att skärmen har stängts. DisposableEffect löser detta problem på ramverksnivå: om utvecklaren har använt DisposableEffect för att registrera en lyssnare, kommer onDispose garanterat att avbryta prenumerationen i vilket scenario som helst av komponentens avslutning.
Detta är särskilt kritiskt för LazyColumn och LazyGrid, där element ständigt skapas och förstörs under scrollning. Utan DisposableEffect skulle varje element som försvinner från det synliga området lämna en aktiv prenumeration. Med DisposableEffect anropas onDispose för varje avlastat element, vilket säkerställer att resurser frigörs omedelbart efter att elementet lämnar skärmen.
Enligt Android Performance Patterns (Google, 2025) minskar användningen av DisposableEffect för alla native-prenumerationer antalet minnesläckor i Compose-applikationer med 60–70% jämfört med manuell hantering via livscykel-callbacks. Systemet självt spårar ögonblicket för att lämna kompositionen och garanterar exekvering av onDispose även vid nödstängning av skärmen.
| Resurs | Vad DisposableEffect gör | Utan DisposableEffect |
|---|---|---|
| BroadcastReceiver | register + onDispose → unregister | Receiver förblir aktiv |
| SensorManager | registerListener + onDispose → unregister | Sensor fortsätter skicka data |
| Observable (inte Flow) | subscribe + onDispose → unregister | Callback håller referens |
| TextureView / SurfaceView | setCallback + onDispose → removeCallback | Callback-läcka |
| Socket / Channel | open + onDispose → close | Anslutning förblir öppen |
Ett av de mest illustrativa exemplen på användning av DisposableEffect — arbete med enhetssensorer (accelerometer, gyroskop, magnetometer). Sensorer kräver obligatorisk avregistrering vid arbetets slut, annars fortsätter de att förbruka batterienergi och skicka data även efter att skärmen stängts.
Praktiskt exempel: applikation för mätning av lutningsvinkel. DisposableEffect(Unit) registrerar en accelerometer-lyssnare när komponenten visas och avregistrerar i onDispose. Sensordata överförs till tillståndet via mutableStateOf, vilket automatiskt uppdaterar UI. Om skärmen scrollas i LazyColumn och elementet försvinner, aktiveras onDispose omedelbart — sensorn slutar skicka data för detta element.
När sensortypen ändras (till exempel från accelerometer till gyroskop) ändras sensorType-nyckeln, onDispose avbryter den gamla prenumerationen och det nya DisposableEffect-blocket registrerar den nya sensorn. Utan nycklar skulle du manuellt behöva kontrollera vilken sensor som tidigare registrerats och anropa unregisterListener med rätt listener — vilket är felbenäget.
@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("Värde: $sensorValue")
}
BroadcastReceiver är ett klassiskt exempel på ett API som kräver ett obligatoriskt register / unregister-par. I en Compose-applikation är DisposableEffect idealisk för att registrera en mottagare under en specifik skärms livstid. Vid inträde på skärmen registreras BroadcastReceiver med lämpligt IntentFilter, vid utträde — avbryts automatiskt i onDispose.
Typiskt scenario — övervakning av nätverksstatus. DisposableEffect registrerar en mottagare på ConnectivityManager som meddelar om förändringar i nätverksanslutningen. När status ändras (WiFi / mobildata / inget nätverk) uppdateras composable-tillståndet och UI visar motsvarande indikator. När skärmen stängs avbryter onDispose garanterat registreringen — även om applikationen går i bakgrunden.
För mottagare med ContextCompat.registerReceiver och flaggan RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) blir användning av DisposableEffect obligatorisk, eftersom systemet kräver explicit angivelse av mottagarens verkningsdomän. DisposableEffect säkerställer att verkningsdomänen är begränsad till skärmens livstid, vilket överensstämmer med säkerhetskraven i nya Android-versioner.
@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) { ... }
}
Det första kritiska misstaget — uteblivet anrop av onDispose. Koden inuti DisposableEffect-blocket måste anropa onDispose, annars uppstår ett kompileringsfel. Utvecklare försöker dock ibland kringgå detta genom att placera onDispose i ett villkor: if (condition) { onDispose { ... } }. Sådan kod kompileras, men onDispose registreras inte om villkoret inte uppfylls — resursen frigörs aldrig.
Det andra misstaget — användning av DisposableEffect för asynkrona operationer. Eftersom DisposableEffect är synkront kan delay()- eller await()-anrop inte skrivas inuti det. Om asynkron initiering med efterföljande rensning behövs, använd en kombination av LaunchedEffect (för dataladdning) och DisposableEffect (för konfigurering/rensning av native-resurser), eller en separat mekanism med rememberCoroutineScope.
Det tredje misstaget — att skapa nya objekt inuti DisposableEffect utan remember. Om objekt (sensor, listener, mottagare) skapas inuti effekten vid varje anrop och nycklarna ändras ofta, leder detta till överdriven objektskapning och sophämtning. Det är bättre att flytta objektskapandet till remember eller remember { ... } utanför DisposableEffect, och inuti effekten endast registrera och avbryta.
Vanliga frågor
DisposableEffect fungerar synkront och tillhandahåller onDispose för explicit rensning av resurser. LaunchedEffect fungerar asynkront i en korutin och avbryter den automatiskt vid nyckeländring eller utträde ur kompositionen. Om resursen kräver anrop av en cleanup-metod (close, unregister, dispose) — använd DisposableEffect. Om operationen är en suspend-funktion — använd LaunchedEffect.
Ja, onDispose är obligatoriskt — Kotlin-kompilatorn kräver dess anrop inuti DisposableEffect-blocket. Om onDispose inte anropas kommer koden inte att kompileras. Detta är avsiktligt utformat för att förhindra utvecklares glömska och säkerställa att varje öppen resurs stängs när kompositionen lämnas.
Använd try-catch inuti DisposableEffect-blocket. Om registrering av resursen kan kasta ett undantag (till exempel sensorn hittades inte), omge det i try och hantera felet i UI via ett separat tillstånd. onDispose måste anropas oavsett framgången av initieringen — placera det i ett finally-block eller i slutet av try-sektionen.
Rekommenderas inte. För Flow är det bättre att använda LaunchedEffect med collectLatest eller metoden .collectAsState() med Lifecycle.repeatOnLifecycle. DisposableEffect stöder inte suspend-funktioner, så en Flow-prenumeration inuti skulle kräva att en separat korutin startas via CoroutineScope, vilket komplicerar koden och ökar risken för läckor.
Det finns inga begränsningar, men det rekommenderas att gruppera relaterade resurser i en DisposableEffect med flera operationer inuti och en onDispose. Om resurserna är oberoende (till exempel sensor och BroadcastReceiver) är det bättre att dela upp dem i separata DisposableEffect med olika nycklar — detta förenklar felsökning och förhindrar oönskad återskapning av alla resurser när en nyckel ändras.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också