DisposableEffect — Ressourcenfreigabe in Jetpack Compose

Autor: IT Sectr Veröffentlicht: 2026-06-30 Lesezeit: 9 Min.

DisposableEffect ist eine composable-Funktion in Jetpack Compose, die für Operationen entwickelt wurde, die eine explizite Initialisierung und anschließende Ressourcenbereinigung erfordern. Im Gegensatz zu anderen Side-Effect-APIs bietet DisposableEffect einen onDispose-Block, der garantiert ausgeführt wird, wenn die Komponente die Komposition verlässt oder sich der Schlüssel ändert. Dies macht es unverzichtbar für die Arbeit mit nativen Abonnements, Sensor-Listenern und Hardwareressourcen. Laut Android Developers Documentation (2025) wird DisposableEffect für alle Szenarien empfohlen, die ein Setup/Teardown-Paar erfordern, analog zu onStart/onStop im Activity-Lebenszyklus.

Das Wichtigste

  • DisposableEffect — Side-Effect-API für Einrichtung und garantierte Ressourcenbereinigung.
  • onDispose — erforderlicher Block, der beim Verlassen der Komposition oder bei Schlüsseländerung ausgeführt wird.
  • Synchron — im Gegensatz zu LaunchedEffect arbeitet DisposableEffect synchron ohne Coroutinen.
  • Bereinigung — typische Szenarien: Abbestellen von LiveData, Schließen von Sockets, Abmelden von BroadcastReceiver.
  • Schlüssel — bei Schlüsseländerung wird onDispose für den alten Wert ausgeführt und eine erneute Initialisierung mit dem neuen Wert durchgeführt.

Was ist DisposableEffect in Jetpack Compose

DisposableEffect ist ein zentrales Werkzeug für die Ressourcenverwaltung in Jetpack Compose. Seine Hauptfunktion ist der garantierte Aufruf des onDispose-Blocks, wenn der Lebenszyklus der composable-Komponente endet. Dieses Verhalten ist für die Android-Entwicklung entscheidend, da nicht geschlossene Abonnements von Systemdiensten zu Speicherlecks und Anwendungsabstürzen führen können.

Im Gegensatz zu LaunchedEffect, das in einem asynchronen Coroutinen-Kontext läuft, wird DisposableEffect synchron ausgeführt. Das bedeutet, dass Sie darin keine suspend-Funktionen aufrufen können. Die synchrone Ausführung gewährleistet Vorhersagbarkeit: Sie können sicher sein, dass Initialisierungscode vor dem ersten Rendern und Bereinigungscode ausgeführt wird, bevor die Komponente aus dem Speicher entfernt wird.

Laut Jetpack Compose Dokumentation (2025) sollte DisposableEffect in vier Hauptszenarien verwendet werden: (1) Abonnieren von Systemdiensten (Sensoren, LocationManager), (2) Registrieren von BroadcastReceiver, (3) Arbeiten mit callback-basierten Bibliotheken, die keine Coroutinen unterstützen, (4) Binden von Compose-Komponenten an Legacy-View-Systeme über 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("Sensor: $value")
}

Wie DisposableEffect mit onDispose funktioniert

Die interne Mechanik von DisposableEffect basiert auf den Phasen des Kompositionslebenszyklus. Wenn eine composable-Komponente in die Komposition eintritt, führt DisposableEffect den übergebenen Codeblock aus. Dieser Block gibt ein DisposableEffectResult-Objekt zurück, das die onDispose-Lambda enthält. Die Komposition speichert dieses Ergebnis und ruft onDispose auf, wenn die Komponente die Komposition verlässt — unabhängig vom Grund (Navigation, Zustandsänderung des Elternobjekts, Entfernung aus LazyColumn).

Der Schlüssel-Mechanismus in DisposableEffect funktioniert ähnlich wie bei LaunchedEffect: Wenn sich ein Schlüssel ändert, wird zunächst onDispose für den alten Zustand ausgeführt, dann der Initialisierungsblock mit den neuen Schlüsseln erneut gestartet. Dies ermöglicht die Neukonfiguration einer Ressource bei Änderung ihrer Parameter. Wenn der Schlüssel beispielsweise eine Socket-URL ist, wird bei deren Änderung der alte Socket geschlossen und ein neuer geöffnet.

Wichtig: Der onDispose-Block ist ein obligatorisches Element von DisposableEffect. Wenn Sie onDispose nicht innerhalb des Blocks aufrufen, wird der Code nicht kompiliert. Diese Compiler-Anforderung stellt sicher, dass der Entwickler die Ressourcenbereinigung nicht vergisst, was eine häufige Fehlerursache bei der manuellen Abonnementverwaltung ist.

kotlin
// Correct usage with a key
DisposableEffect(sensorType) {
    val sensor = sensorManager.getDefaultSensor(sensorType)
    sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
    
    onDispose {
        sensorManager.unregisterListener(listener)
    }
}

// Multiple resources in one DisposableEffect
DisposableEffect(Unit) {
    context.registerReceiver(receiver, intentFilter)
    lifecycle.addObserver(observer)
    
    onDispose {
        context.unregisterReceiver(receiver)
        lifecycle.removeObserver(observer)
    }
}

DisposableEffect gegen Speicherlecks

Speicherlecks in Android-Anwendungen entstehen häufig durch nicht abgemeldete Listener und Abonnements, die weiterhin eine Referenz auf eine Activity oder einen Context halten, nachdem der Bildschirm geschlossen wurde. DisposableEffect löst dieses Problem auf Framework-Ebene: Wenn der Entwickler DisposableEffect zum Registrieren eines Listeners verwendet, garantiert onDispose, dass das Abonnement in jedem Szenario des Komponentenendes gekündigt wird.

Dies ist besonders wichtig für LazyColumn und LazyGrid, bei denen Elemente während des Scrollens ständig erstellt und zerstört werden. Ohne DisposableEffect würde jedes Element, das aus dem sichtbaren Bereich verschwindet, ein aktives Abonnement hinterlassen. Mit DisposableEffect wird onDispose für jedes entladene Element aufgerufen, wodurch sichergestellt wird, dass Ressourcen sofort freigegeben werden, nachdem das Element den Bildschirm verlassen hat.

Laut Android Performance Patterns (Google, 2025) reduziert die Verwendung von DisposableEffect für alle nativen Abonnements die Anzahl der Speicherlecks in Compose-Anwendungen um 60–70% im Vergleich zur manuellen Verwaltung über Lebenszyklus-Callbacks. Das System selbst verfolgt den Zeitpunkt, zu dem die Komponente die Komposition verlässt, und garantiert die Ausführung von onDispose auch bei notfallmäßigem Bildschirmschluss.

RessourceWas DisposableEffect machtOhne DisposableEffect
BroadcastReceiverregister + onDispose → unregisterReceiver bleibt aktiv
SensorManagerregisterListener + onDispose → unregisterListenerSensor sendet weiterhin Daten
Observable (nicht Flow)subscribe + onDispose → unsubscribeCallback hält Referenz
TextureView / SurfaceViewsetCallback + onDispose → removeCallbackCallback-Leck
Socket / Channelopen + onDispose → closeVerbindung bleibt offen

Sensor-Abonnement via DisposableEffect

Eines der anschaulichsten Beispiele für die Verwendung von DisposableEffect ist die Arbeit mit Gerätesensoren (Beschleunigungsmesser, Gyroskop, Magnetometer). Sensoren erfordern eine obligatorische Abmeldung bei Beendigung der Arbeit, da sie sonst weiterhin Batteriestrom verbrauchen und Daten senden, selbst nachdem der Bildschirm geschlossen wurde.

Praktisches Beispiel: eine Anwendung zur Messung des Neigungswinkels. DisposableEffect(Unit) registriert einen Beschleunigungsmesser-Listener beim Erscheinen der Komponente und meldet ihn in onDispose ab. Sensordaten werden über mutableStateOf an den Zustand übergeben, was die UI automatisch aktualisiert. Wenn der Bildschirm in LazyColumn gescrollt wird und das Element verschwindet, wird onDispose sofort ausgelöst — der Sensor hört auf, Daten für dieses Element zu senden.

Beim Wechsel des Sensortyps (z. B. von Beschleunigungsmesser zu Gyroskop) ändert sich der Schlüssel sensorType, onDispose kündigt das alte Abonnement und der neue DisposableEffect-Block registriert den neuen Sensor. Ohne Schlüssel müssten Sie manuell überprüfen, welcher Sensor zuvor registriert war, und unregisterListener mit dem richtigen Listener aufrufen — was fehleranfällig ist.

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

BroadcastReceiver-Registrierung via DisposableEffect

BroadcastReceiver ist ein klassisches Beispiel für eine API, die ein obligatorisches register / unregister-Paar erfordert. In einer Compose-Anwendung ist DisposableEffect ideal für die Registrierung eines Receivers für die Lebensdauer eines bestimmten Bildschirms. Beim Betreten des Bildschirms wird ein BroadcastReceiver mit dem erforderlichen IntentFilter registriert; beim Verlassen wird er automatisch in onDispose abgemeldet.

Ein typisches Szenario ist die Überwachung des Netzwerkstatus. DisposableEffect registriert einen Receiver für ConnectivityManager, der über Änderungen der Netzwerkverbindung informiert. Wenn sich der Status ändert (WiFi / mobile Daten / kein Netzwerk), wird der composable-Zustand aktualisiert und die UI zeigt den entsprechenden Indikator an. Wenn der Bildschirm geschlossen wird, garantiert onDispose die Abmeldung — selbst wenn die App in den Hintergrund geht.

Für Receiver mit ContextCompat.registerReceiver und dem Flag RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED (Android 14+) wird die Verwendung von DisposableEffect obligatorisch, da das System eine explizite Angabe des Geltungsbereichs des Receivers erfordert. DisposableEffect stellt sicher, dass der Geltungsbereich auf die Lebensdauer des Bildschirms beschränkt ist, was mit den Sicherheitsanforderungen neuerer Android-Versionen übereinstimmt.

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) { ... }
}

Häufige Fehler mit DisposableEffect

Der erste kritische Fehler ist das Fehlen des onDispose-Aufrufs. Der Code innerhalb des DisposableEffect-Blocks muss onDispose aufrufen, andernfalls tritt ein Kompilierungsfehler auf. Entwickler versuchen jedoch manchmal, dies zu umgehen, indem sie onDispose in eine Bedingung setzen: if (condition) { onDispose { ... } }. Dieser Code wird kompiliert, aber onDispose wird nicht registriert, wenn die Bedingung nicht erfüllt ist — die Ressource wird niemals freigegeben.

Der zweite Fehler ist die Verwendung von DisposableEffect für asynchrone Operationen. Da DisposableEffect synchron ist, können Sie darin keine delay()- oder await()-Aufrufe schreiben. Wenn Sie eine asynchrone Initialisierung mit anschließender Bereinigung benötigen, verwenden Sie eine Kombination aus LaunchedEffect (für Datenladung) und DisposableEffect (für Einrichtung/Bereinigung nativer Ressourcen) oder einen separaten Mechanismus mit rememberCoroutineScope.

Der dritte Fehler ist das Erstellen neuer Objekte innerhalb von DisposableEffect ohne remember. Wenn Objekte (Sensor, Listener, Receiver) bei jedem Aufruf innerhalb des Effekts erstellt werden und sich Schlüssel häufig ändern, führt dies zu übermäßiger Objekterstellung und Garbage Collection. Es ist besser, die Objekterstellung in remember oder remember { ... } außerhalb von DisposableEffect zu verschieben und nur die Registrierung und Abmeldung innerhalb des Effekts durchzuführen.

Häufig gestellte Fragen

Was ist der Unterschied zwischen DisposableEffect und LaunchedEffect?

DisposableEffect arbeitet synchron und bietet onDispose zur expliziten Ressourcenbereinigung. LaunchedEffect arbeitet asynchron in einer Coroutine und bricht diese automatisch ab, wenn der Schlüssel wechselt oder die Komposition verlassen wird. Wenn eine Ressource einen Bereinigungsmethodenaufruf (close, unregister, dispose) erfordert — verwenden Sie DisposableEffect. Wenn die Operation eine suspend-Funktion ist — verwenden Sie LaunchedEffect.

Ist der onDispose-Block in DisposableEffect obligatorisch?

Ja, onDispose ist obligatorisch — der Kotlin-Compiler verlangt seinen Aufruf innerhalb des DisposableEffect-Blocks. Wenn Sie onDispose nicht aufrufen, wird der Code nicht kompiliert. Dies wurde absichtlich so gestaltet, um zu verhindern, dass Entwickler es vergessen, und um sicherzustellen, dass jede geöffnete Ressource beim Verlassen der Komposition ordnungsgemäß geschlossen wird.

Wie behandelt man Fehler innerhalb von DisposableEffect?

Verwenden Sie try-catch innerhalb des DisposableEffect-Blocks. Wenn die Ressourcenregistrierung eine Ausnahme auslösen kann (z. B. Sensor nicht gefunden), umschließen Sie sie mit try und behandeln Sie den Fehler in der UI über einen separaten Zustand. onDispose sollte unabhängig vom Erfolg der Initialisierung aufgerufen werden — setzen Sie es in einen finally-Block oder an das Ende des try-Abschnitts.

Kann ich DisposableEffect zum Abonnieren von Flow verwenden?

Nicht empfohlen. Für Flow verwenden Sie besser LaunchedEffect mit collectLatest oder die .collectAsState()-Methode mit Lifecycle.repeatOnLifecycle. DisposableEffect unterstützt keine suspend-Funktionen, daher würde das Abonnieren von Flow darin das Starten einer separaten Coroutine über CoroutineScope erfordern, was den Code verkompliziert und das Risiko von Lecks erhöht.

Wie viele DisposableEffects kann es in einem Composable geben?

Es gibt keine Grenzen, aber es wird empfohlen, verwandte Ressourcen in einem DisposableEffect mit mehreren Operationen und einem onDispose zu gruppieren. Wenn Ressourcen unabhängig sind (z. B. Sensor und BroadcastReceiver), teilen Sie sie besser in separate DisposableEffects mit verschiedenen Schlüsseln auf — dies vereinfacht das Debugging und verhindert die unerwünschte Neuerstellung aller Ressourcen bei Änderung eines Schlüssels.

Zusammenfassung

  • DisposableEffect — Jetpack Compose Side-Effect-API für synchrone Initialisierung mit garantierter Bereinigung via onDispose.
  • onDispose — erforderlicher Block, der beim Verlassen der Komposition oder bei Schlüsseländerung ausgeführt wird und Speicherlecks verhindert.
  • Schlüssel — bei Schlüsseländerung wird zuerst onDispose für den alten Wert ausgeführt, dann erfolgt die erneute Initialisierung mit dem neuen.
  • Synchron — DisposableEffect wird synchron ausgeführt; suspend-Funktionen sind darin nicht verfügbar.
  • Typische Szenarien — BroadcastReceiver, Sensoren, native Listener, callback-basierte Bibliotheken, AndroidView-Integration.
  • Lecks — DisposableEffect reduziert die Anzahl der Lecks um 60–70% im Vergleich zur manuellen Verwaltung von Lebenszyklus-Callbacks.
  • Fehler — Hauptrisiken: bedingter onDispose-Aufruf, Verwendung für asynchrone Operationen, Objekterstellung ohne remember innerhalb des Effekts.

Wir entwickeln eine mobile Applikation schlüsselfertig

IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.

Projekt besprechen

Lesen Sie auch