Reaktive Programmierung in Kotlin: Die Kraft von kotlinx.coroutines und Flow
\n\nReaktive Programmierung ist ein Paradigma, das auf die asynchrone Verarbeitung von Datenströmen und die Verbreitung von Änderungen ausgerichtet ist. Im Kotlin-Ökosystem ist die Bibliothek kotlinx.coroutines das wichtigste Werkzeug zur Implementierung reaktiver Muster, genauer gesagt ihre Komponente Flow. Im Gegensatz zu RxJava ist Flow eng in Coroutinen integriert, was den Code prägnanter und sicherer macht.
Grundlegende Konzepte von Flow
\n\nFlow ist ein kalter asynchroner Datenstrom, der Werte sequenziell emittiert. Er unterstützt:
\n- \n
- Kalte Ströme — Daten werden nur dann generiert, wenn ein Abonnent (terminaler Operator) vorhanden ist. \n
- Ausführungskontext — Der Strom kann zwischen verschiedenen Coroutinen-Dispatchern wechseln. \n
- Abbruch durch Coroutinen — Der Abbruch der übergeordneten Coroutine beendet Flow automatisch. \n
Installation und Einrichtung
\nUm Flow zu verwenden, fügen Sie die Abhängigkeit in build.gradle.kts hinzu:
dependencies {\n implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")\n}\n\nErstellen und Konsumieren von Flow
\nErstellen wir einen einfachen Zahlenstrom mit Verzögerung:
\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.flow.*\n\nfun simpleFlow(): Flow<Int> = flow {\n for (i in 1..5) {\n delay(100) // Simulation asynchroner Arbeit\n emit(i) // Wert emittieren\n }\n}\n\nfun main() = runBlocking {\n simpleFlow().collect { value ->\n println("Erhalten: $value")\n }\n}\nIn diesem Beispiel erstellt flow { ... } einen kalten Strom, und collect ist der terminale Operator, der die Ausführung startet.
Datentransformation mit Operatoren
\nFlow bietet eine umfangreiche Sammlung von Operatoren, ähnlich wie RxJava:
\n- \n
map— transformiert jeden Wert. \n filter— filtert Werte nach einer Bedingung. \n flatMapConcat— verbindet Ströme sequenziell. \n catch— behandelt Ausnahmen innerhalb des Stroms. \n
Beispiel mit Operatoren:
\nfun transformFlow(): Flow<String> = flow {\n emit("Kotlin")\n emit("Java")\n emit("Python")\n}.map { it.lowercase() }\n .filter { it.startsWith("k") }\n\nfun main() = runBlocking {\n transformFlow().collect { println(it) }\n // Ausgabe: kotlin\n}\n\nFehlerbehandlung und Abschluss
\nFlow ermöglicht die elegante Behandlung von Ausnahmen mit catch und die Ausführung von Abschlussaktionen über onCompletion:
fun errorProneFlow(): Flow<Int> = flow {\n emit(1)\n emit(2)\n throw RuntimeException("Fehler!")\n emit(3) // wird nicht ausgeführt\n}\n\nfun main() = runBlocking {\n errorProneFlow()\n .catch { e -> println("Fehler abgefangen: ${e.message}") }\n .onCompletion { println("Strom abgeschlossen") }\n .collect { println(it) }\n}\n// Ausgabe: 1, 2, Fehler abgefangen: Fehler!, Strom abgeschlossen\n\nHeiße Ströme: StateFlow und SharedFlow
\nIm Gegensatz zum kalten Flow sind StateFlow und SharedFlow heiße Ströme, die einen Zustand speichern und mehrere Abonnenten haben können.
\n\nStateFlow
\nWird zum Speichern und Beobachten des Zustands verwendet (z. B. in ViewModel):
\nclass CounterViewModel {\n private val _counter = MutableStateFlow(0)\n val counter: StateFlow<Int> = _counter.asStateFlow()\n\n fun increment() {\n _counter.value++\n }\n}\n\n// In Activity oder Fragment\nlifecycleScope.launch {\n viewModel.counter.collect { value ->\n textView.text = "Zähler: $value"\n }\n}\n\nSharedFlow
\nGeeignet für