MWCodebymw.de ↗
Kotlin

rememberUpdatedState in Compose – wenn dein Callback im Effekt einfriert

Ein LaunchedEffect mit key1 = Unit startet genau einmal – und hält damit für immer die Lambda-Referenz vom ersten Durchlauf fest. Klickt der Nutzer später etwas anderes an, passiert das Falsche. rememberUpdatedState löst genau das.

Der Fehler sieht harmlos aus und ist deshalb so gemein: Ein Effekt soll genau einmal starten, bekommt also Unit als Schlüssel – und ruft am Ende ein Lambda auf, das von außen hereingereicht wurde. Sobald sich dieses Lambda ändert, ruft der Effekt trotzdem noch das alte auf. Ich habe das selbst schon zweimal gebaut, bevor ich verstanden habe, warum.

Der Fehler

Ein Splash-Screen, der nach drei Sekunden weiterspringt:

@Composable
fun Splash(onTimeout: () -> Unit) {
    LaunchedEffect(Unit) {          // absichtlich nur EINMAL starten
        delay(3_000)
        onTimeout()                 // ⚠️ das Lambda vom ERSTEN Durchlauf
    }
    Text("Moment …")
}

LaunchedEffect(Unit) tut genau das, was draufsteht: Der Block läuft einmal an und wird beim Neuzeichnen nicht neu gestartet. Damit bleibt aber auch die Referenz auf onTimeout eingefroren, so wie sie beim ersten Durchlauf war. Rendert der Aufrufer neu und übergibt ein anderes Lambda – etwa weil der Ziel-Bildschirm inzwischen ein anderer ist –, landet der Nutzer nach drei Sekunden am falschen Ort.

Der naheliegende „Fix" macht es schlimmer:

LaunchedEffect(onTimeout) { … }     // ❌ Timer startet bei jeder neuen Lambda-Instanz neu

Ein Lambda, das im Aufrufer inline geschrieben ist, ist bei jeder Recomposition ein neues Objekt. Der Schlüssel ändert sich also ständig, der Effekt wird abgebrochen und neu gestartet – und die drei Sekunden fangen jedes Mal von vorne an. Im schlimmsten Fall laufen sie nie ab.

Die Lösung

rememberUpdatedState gibt dir ein State-Objekt, dessen Wert bei jeder Recomposition auf den aktuellen aktualisiert wird, ohne dass sich das Objekt selbst ändert. Der Effekt liest darüber immer den frischen Wert und startet trotzdem nie neu:

@Composable
fun Splash(onTimeout: () -> Unit) {
    val currentOnTimeout by rememberUpdatedState(onTimeout)

    LaunchedEffect(Unit) {          // läuft weiterhin nur EINMAL
        delay(3_000)
        currentOnTimeout()          // ✅ immer das aktuelle Lambda
    }
    Text("Moment …")
}

Die Aufteilung ist der ganze Trick: Der Schlüssel entscheidet, wann der Effekt neu startet. rememberUpdatedState entscheidet, welchen Wert er dabei sieht. Beides voneinander zu trennen ist genau das, was hier fehlte.

Wichtig sind die Importe – by braucht die Delegat-Erweiterung:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState

Wo mir das im Alltag begegnet

Der Splash-Screen ist nur das Lehrbuchbeispiel. In echten Apps trifft es mich vor allem hier:

@Composable
fun AutoSave(text: String, onSave: (String) -> Unit) {
    val currentOnSave by rememberUpdatedState(onSave)
    val currentText by rememberUpdatedState(text)

    LaunchedEffect(Unit) {          // eine Schleife für die gesamte Lebenszeit
        while (true) {
            delay(30_000)
            currentOnSave(currentText)   // speichert IMMER den aktuellen Stand
        }
    }
}

Ohne rememberUpdatedState würde dieses Auto-Save alle 30 Sekunden brav den Text speichern, den der Nutzer beim Öffnen des Bildschirms hatte. Der Fehler fällt im Test nicht auf, weil man beim Prüfen ja meist nichts ändert – und im Alltag schon.

Dasselbe Muster gilt für DisposableEffect: Auch ein Listener, den du nur einmal registrierst, ruft sonst dauerhaft das eingefrorene Lambda auf.

Wann du es NICHT brauchst

  • Wenn der Effekt ohnehin bei jeder Änderung neu starten soll – dann gehört der Wert schlicht in den Schlüssel: LaunchedEffect(userId) { … }.
  • Für State, Flow oder ViewModel-Referenzen, die du direkt im Effekt liest. Die sind selbst schon beobachtbar oder stabil.
  • Für einfache Werte, die sich nie ändern.

Die Faustregel, die ich mir gemerkt habe: Sobald in einem Effekt mit fixem Schlüssel ein Lambda oder ein veränderlicher Parameter von außen aufgerufen wird, gehört ein rememberUpdatedState davor.

Wenn du eine Android-App planst, bei der solche Details von Anfang an sitzen statt später mühsam gesucht zu werden: schreib mir einfach.

Quellen

#Compose#Android#rememberUpdatedState#LaunchedEffect#Kotlin

Du brauchst mehr als ein Snippet?

Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.

Projekt anfragen →