DisposableEffect is een composable-functie in Jetpack Compose bedoeld voor bewerkingen die expliciete initialisatie en daaropvolgende opschoning van resources vereisen. In tegenstelling tot andere side-effect API's biedt DisposableEffect een onDispose-blok dat gegarandeerd wordt uitgevoerd wanneer de component de compositie verlaat of wanneer de sleutel verandert. Dit maakt het onmisbaar voor het werken met native abonnementen, sensor-luisteraars en hardware-resources. Volgens Android Developers Documentation (2025) wordt DisposableEffect aanbevolen in alle scenario's waar een setup/teardown-paar nodig is, vergelijkbaar met onStart/onStop in de Activity-levenscyclus.
Belangrijkste punten
DisposableEffect is een essentieel hulpmiddel voor resourcebeheer in Jetpack Compose. Het belangrijkste kenmerk is de gegarandeerde aanroep van het onDispose-blok aan het einde van de levenscyclus van de composable-component. Dit gedrag is kritisch voor Android-ontwikkeling, waar niet-gesloten abonnementen op systeemdiensten kunnen leiden tot geheugenlekken en crashes van de applicatie.
In tegenstelling tot LaunchedEffect, dat werkt in een asynchrone coroutine-context, wordt DisposableEffect synchroon uitgevoerd. Dit betekent dat er geen suspend-functies binnen kunnen worden aangeroepen. Synchroniteit zorgt voor voorspelbaarheid: u kunt er zeker van zijn dat de initialisatiecode vóór de eerste weergave wordt uitgevoerd en de opschoningscode voordat de component uit het geheugen wordt verwijderd.
Volgens de Jetpack Compose-documentatie (2025) moet DisposableEffect in vier hoofdscenario's worden gebruikt: (1) abonneren op systeemdiensten (sensoren, LocationManager), (2) registreren van BroadcastReceiver, (3) werken met callback-gebaseerde bibliotheken die geen coroutines ondersteunen, (4) koppelen van Compose-componenten aan Legacy View-systemen 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")
}
De interne werking van DisposableEffect is gebaseerd op de fasen van de compositie-levenscyclus. Wanneer een composable-component de compositie binnengaat, roept DisposableEffect het doorgegeven codeblok aan. Dit blok retourneert een DisposableEffectResult-object met daarin de onDispose-lambda. De compositie bewaart dit resultaat en roept onDispose aan op het moment dat de component de compositie verlaat — ongeacht de reden (navigatie, verandering van ouderstatus, verwijdering uit LazyColumn).
Het sleutelmechanisme in DisposableEffect werkt analoog aan LaunchedEffect: bij wijziging van een sleutel wordt eerst onDispose uitgevoerd voor de oude toestand, waarna het initialisatieblok opnieuw wordt gestart met de nieuwe sleutels. Dit maakt het mogelijk om de resource opnieuw te configureren wanneer de parameters veranderen. Als de sleutel bijvoorbeeld de socket-URL is, wordt bij wijziging de oude socket gesloten en een nieuwe geopend.
Belangrijk: het onDispose-blok is een verplicht onderdeel van DisposableEffect. Als onDispose niet binnen het blok wordt aangeroepen, zal de code niet compileren. Deze compiler-eis garandeert dat de ontwikkelaar niet vergeet om opschoning van de resource te voorzien, wat een veelvoorkomende oorzaak van fouten is bij handmatig beheer van abonnementen.
// Correct gebruik met een sleutel
DisposableEffect(sensorType) {
val sensor = sensorManager.getDefaultSensor(sensorType)
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
onDispose {
sensorManager.unregisterListener(listener)
}
}
// Meerdere resources in één DisposableEffect
DisposableEffect(Unit) {
context.registerReceiver(receiver, intentFilter)
lifecycle.addObserver(observer)
onDispose {
context.unregisterReceiver(receiver)
lifecycle.removeObserver(observer)
}
}
Geheugenlekken in Android-applicaties ontstaan vaak door niet-geregistreerde luisteraars en abonnementen die een verwijzing naar Activity of Context blijven vasthouden nadat het scherm is gesloten. DisposableEffect lost dit probleem op framework-niveau op: als de ontwikkelaar DisposableEffect heeft gebruikt voor het registreren van een luisteraar, zal onDispose het abonnement gegarandeerd annuleren bij elk scenario van componentbeëindiging.
Dit is vooral kritisch voor LazyColumn en LazyGrid, waar elementen tijdens het scrollen voortdurend worden gemaakt en vernietigd. Zonder DisposableEffect zou elk element dat uit het zicht verdwijnt een actief abonnement achterlaten. Met DisposableEffect wordt onDispose voor elk verwijderd element aangeroepen, waardoor resources onmiddellijk worden vrijgegeven zodra het element het scherm verlaat.
Volgens Android Performance Patterns (Google, 2025) vermindert het gebruik van DisposableEffect voor alle native abonnementen het aantal geheugenlekken in Compose-applicaties met 60–70% in vergelijking met handmatig beheer via lifecycle-callbacks. Het systeem volgt zelf het moment van verlaten van de compositie en garandeert de uitvoering van onDispose, zelfs bij noodsluiting van het scherm.
| Resource | Wat DisposableEffect doet | Zonder DisposableEffect |
|---|---|---|
| BroadcastReceiver | register + onDispose → unregister | Receiver blijft actief |
| SensorManager | registerListener + onDispose → unregister | Sensor blijft gegevens sturen |
| Observable (geen Flow) | subscribe + onDispose → unsubscribe | Callback houdt verwijzing vast |
| TextureView / SurfaceView | setCallback + onDispose → removeCallback | Callback-lek |
| Socket / Channel | open + onDispose → close | Verbinding blijft open |
Een van de meest illustratieve voorbeelden van het gebruik van DisposableEffect — werken met apparaatsensoren (accelerometer, gyroscoop, magnetometer). Sensoren vereisen verplichte annulering van de registratie bij beëindiging, anders blijven ze batterijvermogen verbruiken en gegevens verzenden, zelfs nadat het scherm is gesloten.
Praktisch voorbeeld: een app voor het meten van de hellingshoek. DisposableEffect(Unit) registreert een accelerometer-luisteraar wanneer de component verschijnt en annuleert de registratie in onDispose. Sensor gegevens worden doorgegeven aan de toestand via mutableStateOf, wat de UI automatisch bijwerkt. Als het scherm in LazyColumn wordt gescrolld en het element verdwijnt, wordt onDispose onmiddellijk geactiveerd — de sensor stopt met het verzenden van gegevens voor dit element.
Bij het wijzigen van het sensortype (bijvoorbeeld van accelerometer naar gyroscoop) verandert de sensorType-sleutel, annuleert onDispose het oude abonnement en registreert het nieuwe DisposableEffect-blok de nieuwe sensor. Zonder sleutels zou u handmatig moeten controleren welke sensor eerder was geregistreerd en unregisterListener met de juiste listener aanroepen — wat foutgevoelig is.
@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("Waarde: $sensorValue")
}
BroadcastReceiver is een klassiek voorbeeld van een API die een verplicht register / unregister-paar vereist. In een Compose-applicatie is DisposableEffect ideaal voor het registreren van een ontvanger voor de levensduur van een specifiek scherm. Bij het betreden van het scherm wordt een BroadcastReceiver met de juiste IntentFilter geregistreerd, bij het verlaten — automatisch geannuleerd in onDispose.
Een typisch scenario — netwerkstatus monitoring. DisposableEffect registreert een ontvanger op ConnectivityManager die op de hoogte stelt van wijzigingen in de netwerkverbinding. Bij statuswijziging (WiFi / mobiele data / geen netwerk) wordt de composable-status bijgewerkt en geeft de UI de juiste indicator weer. Wanneer het scherm wordt gesloten, annuleert onDispose de registratie gegarandeerd — zelfs als de app naar de achtergrond gaat.
Voor ontvangers met ContextCompat.registerReceiver en de vlag RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) wordt het gebruik van DisposableEffect verplicht, omdat het systeem expliciete specificatie van het werkingsdomein van de ontvanger vereist. DisposableEffect garandeert dat het werkingsdomein beperkt is tot de levensduur van het scherm, wat overeenkomt met de beveiligingseisen van nieuwe Android-versies.
@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) { ... }
}
De eerste kritieke fout — het ontbreken van een onDispose-aanroep. De code binnen het DisposableEffect-blok moet onDispose aanroepen, anders treedt er een compilatiefout op. Ontwikkelaars proberen dit echter soms te omzeilen door onDispose in een voorwaarde te plaatsen: if (condition) { onDispose { ... } }. Dergelijke code wordt gecompileerd, maar onDispose wordt niet geregistreerd als de voorwaarde niet wordt vervuld — de resource wordt nooit vrijgegeven.
De tweede fout — het gebruik van DisposableEffect voor asynchrone bewerkingen. Omdat DisposableEffect synchroon is, kunnen er geen delay()- of await()-aanroepen binnen worden geschreven. Als asynchrone initialisatie met latere opschoning nodig is, gebruik dan een combinatie van LaunchedEffect (voor het laden van gegevens) en DisposableEffect (voor het instellen/opschonen van native resources) of een apart mechanisme met rememberCoroutineScope.
De derde fout — het maken van nieuwe objecten binnen DisposableEffect zonder remember. Als er binnen het effect bij elke aanroep objecten (sensor, listener, ontvanger) worden gemaakt en de sleutels vaak veranderen, leidt dit tot overmatige objectcreatie en garbage collection. Het is beter om het maken van objecten naar remember of remember { ... } buiten DisposableEffect te verplaatsen en binnen het effect alleen te registreren en te annuleren.
Veelgestelde vragen
DisposableEffect werkt synchroon en biedt onDispose voor expliciete opschoning van resources. LaunchedEffect werkt asynchroon in een coroutine en annuleert deze automatisch bij sleutelwijziging of het verlaten van de compositie. Als een resource een cleanup-methode vereist (close, unregister, dispose) — gebruik dan DisposableEffect. Als de bewerking een suspend-functie is — gebruik dan LaunchedEffect.
Ja, onDispose is verplicht — de Kotlin-compiler vereist de aanroep ervan binnen het DisposableEffect-blok. Als onDispose niet wordt aangeroepen, wordt de code niet gecompileerd. Dit is opzettelijk ontworpen om vergeetachtigheid van ontwikkelaars te voorkomen en te garanderen dat elke geopende resource wordt gesloten bij het verlaten van de compositie.
Gebruik try-catch binnen het DisposableEffect-blok. Als de registratie van een resource een uitzondering kan veroorzaken (bijvoorbeeld sensor niet gevonden), wikkel het dan in try en verwerk de fout in de UI via een aparte toestand. onDispose moet worden aangeroepen ongeacht het succes van de initialisatie — plaats het in een finally-blok of aan het einde van de try-sectie.
Wordt niet aanbevolen. Gebruik voor Flow liever LaunchedEffect met collectLatest of de methode .collectAsState() met Lifecycle.repeatOnLifecycle. DisposableEffect ondersteunt geen suspend-functies, dus een Flow-abonnement binnen zou het starten van een aparte coroutine via CoroutineScope vereisen, wat de code compliceert en het risico op lekken vergroot.
Er zijn geen beperkingen, maar het wordt aanbevolen om gerelateerde resources te groeperen in één DisposableEffect met meerdere bewerkingen erin en één onDispose. Als resources onafhankelijk zijn (bijvoorbeeld sensor en BroadcastReceiver), is het beter om ze te splitsen in afzonderlijke DisposableEffect met verschillende sleutels — dit vereenvoudigt debugging en voorkomt ongewenst opnieuw maken van alle resources bij het wijzigen van één sleutel.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook