callbackFlow – Listener-APIs in einen Flow verwandeln
Android steckt voller Callback-APIs mit `register`/`unregister`. Mit `callbackFlow` machst du daraus einen ganz normalen Flow – inklusive automatischem Abmelden, wenn niemand mehr zuhört.
Halb Android besteht aus Callbacks: Sensoren, Standort, SharedPreferences, ConnectivityManager, Bluetooth. Immer dasselbe Muster – register…Listener beim Start, unregister…Listener beim Aufräumen. Und genau das Aufräumen vergisst man, wenn man es von Hand macht.
callbackFlow dreht das um: Du beschreibst einmal, wie an- und abgemeldet wird, und bekommst dafür einen Flow, den du wie jeden anderen behandeln kannst – mit map, debounce, collectAsStateWithLifecycle. Das Abmelden passiert automatisch, sobald der Sammler verschwindet.
Das Grundmuster
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
fun ConnectivityManager.netzStatus(): Flow<Boolean> = callbackFlow {
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { trySend(true) }
override fun onLost(network: Network) { trySend(false) }
}
registerDefaultNetworkCallback(callback) // anmelden
awaitClose { unregisterNetworkCallback(callback) } // abmelden – IMMER
}Drei Dinge machen das Muster aus:
trySend(...)stattsend(...): Callbacks sind keine Coroutinen, dürfen also nicht warten.trySendgibt den Wert ohne Suspendieren weiter und verwirft ihn im Zweifel, statt zu blockieren.awaitClose { … }ist Pflicht. Der Block hält den Flow offen und läuft genau dann, wenn der Sammler abbricht – normal, per Exception oder durchcancel(). OhneawaitClosewirftcallbackFlowbeim ersten Sammeln eineIllegalStateException.- Der Rückgabetyp ist ein stinknormaler
Flow. Alles, was du sonst mit Flows machst, geht auch hier.
Was der Callback zu schnell liefert
Sensoren feuern gern hundertmal pro Sekunde. Dagegen hilft ein Puffer mit klarer Regel statt eines wachsenden Rückstaus:
fun SensorManager.beschleunigung(sensor: Sensor): Flow<FloatArray> = callbackFlow {
val listener = object : SensorEventListener {
override fun onSensorChanged(e: SensorEvent) { trySend(e.values.clone()) }
override fun onAccuracyChanged(s: Sensor, genauigkeit: Int) {}
}
registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI)
awaitClose { unregisterListener(listener) }
}.buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)DROP_OLDEST heißt: Der neueste Messwert gewinnt, alte fliegen raus. Bei Sensordaten ist das fast immer richtig – ein zwei Sekunden alter Wert nützt niemandem.
Wichtig ist auch das clone(). Android recycelt das values-Array zwischen den Aufrufen; wer die Referenz weiterreicht, bekommt Werte, die sich hinter seinem Rücken ändern. Das ist ein Fehler, den man beim Lesen nicht sieht und beim Debuggen eine Stunde sucht.
In Compose sammeln
@Composable
fun NetzHinweis(cm: ConnectivityManager) {
val online by remember { cm.netzStatus() }
.collectAsStateWithLifecycle(initialValue = true)
if (!online) {
Text("Offline – Änderungen werden später gesendet.")
}
}Verlässt der Screen die Anzeige, endet das Sammeln, awaitClose läuft, der Listener ist ab. Ohne dass du irgendwo ein onDestroy überschreiben musst.
Ein Hinweis noch, wenn mehrere Stellen denselben Flow brauchen: callbackFlow meldet sich pro Sammler einmal an. Zwei Composables heißt zwei Registrierungen. Willst du das nicht, hängst du ein .shareIn(scope, SharingStarted.WhileSubscribed(5000), replay = 1) dran – dann teilen sich alle eine Anmeldung.
Ich baue solche Brücken in fast jeder App, die mit Hardware oder Systemdiensten spricht. Wenn du eine Android-App in Kotlin & Compose planst, melde dich über bymw.de.
Quellen
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 →Verwandte Snippets
Fehler in Flows abfangen – catch, retry und ein sinnvoller Fallback
Ein try/catch um einen Flow herum fängt nichts. Fehler laufen im Flow selbst mit und werden dort behandelt – mit catch für die Ausgabe und retry für den zweiten Versuch bei Netzproblemen.
WorkManager – Arbeit, die auch läuft, wenn die App zu ist
Daten hochladen, nachts synchronisieren, eine Erinnerung schicken: Sobald etwas laufen soll, während die App geschlossen ist, ist WorkManager das richtige Werkzeug – und nicht ein Thread, den das System sowieso abräumt.
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.