StateFlow — ein reaktiver Zustandscontainer aus der Kotlin Coroutines-Bibliothek, der StateFlow<T> darstellt — ein Subtyp von Flow, der immer den aktuellen Wert speichert und an neue Abonnenten ausgibt. Wir erklären das Wesen von StateFlow: Im Gegensatz zu LiveData ist StateFlow nicht an das Android-Framework gebunden und funktioniert auf jeder Kotlin-Plattform. Laut Google (Android Developers, 2025) wird StateFlow als primäre Alternative zu LiveData für neue Projekte in reinem Kotlin empfohlen, insbesondere in der MVVM-Architektur mit Jetpack Compose.
Wichtige Punkte
collectAsState() in Compose oder repeatOnLifecycle() in View verwendet.StateFlow ist ein Interface aus der Bibliothek kotlinx.coroutines.flow, das MutableSharedFlow mit einem festen replay-Parameter von 1 erweitert. Das bedeutet, dass StateFlow sich immer den zuletzt gesendeten Wert merkt und ihn sofort jedem neuen Abonnenten wiedergibt. Im Gegensatz zu LiveData ist StateFlow Teil der standardmäßigen Kotlin Coroutines-Bibliothek und hat keine Android-Abhängigkeiten.
Konzeptionell ist StateFlow eine reaktive Eigenschaft: Sie lesen seinen aktuellen Wert über .value und abonnieren Änderungen über .collect(). Dieses Modell wird als "Hot Flow" bezeichnet — die Datenquelle ist unabhängig von Abonnenten aktiv, im Gegensatz zu "Cold"-Flows, die über flow { } erstellt werden und beim Erscheinen eines Abonnenten starten.
StateFlow wurde in kotlinx.coroutines 1.3.7 (Dezember 2020) stabilisiert und von Google ab Google I/O 2021 als Ersatz für LiveData empfohlen. Bis Januar 2025 verwenden laut einer JetBrains-Umfrage 56% der neuen Android-Projekte in Kotlin StateFlow als primären reaktiven Container.
Die Wahl zwischen StateFlow und LiveData hängt von der Projektarchitektur, dem Technologiestapel und den Anforderungen an die Plattformunabhängigkeit ab. Nachfolgend ein Vergleich anhand von sechs Hauptkriterien.
| Kriterium | StateFlow | LiveData |
|---|---|---|
| Plattform | Kotlin Multiplatform (Android, iOS, Server) | Nur Android |
| Lifecycle-aware | Nein — erfordert repeatOnLifecycle() | Ja — integrierte Bindung |
| Koroutinen | Vollständige Unterstützung (map, filter, combine) | Über liveData { } Builder |
| Null-Sicherheit | Ja — serialisierbar über kotlinx.serialization | Ja — über nullfähiges LiveData<String?> |
| Konflation | Konfliert — überspringt Zwischenwerte | Nur über postValue() |
| Testen | runTest + Turbine oder integrierte Operatoren | InstantTaskExecutorRule + observeForever |
StateFlow erfordert eine explizite Abonnementverwaltung in der View-Ebene: In Fragment/Activity erfolgt das Abonnement über repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } }. Dies gibt mehr Kontrolle als das automatische Abonnement von LiveData, fügt aber Boilerplate-Code hinzu. In Jetpack Compose wird das Abonnement auf val state by viewModel.uiState.collectAsState() vereinfacht.
Google-Empfehlung (Android Developers, 2025): Verwenden Sie für neue Kotlin-Projekte StateFlow, insbesondere bei der Arbeit mit Compose. Behalten Sie LiveData für: (1) Java-Code, (2) Bibliotheken, die Java-Kompatibilität erfordern, (3) Room DAO (LiveData als DAO-Rückgabetyp ist immer noch beliebt).
MutableStateFlow ist eine veränderbare Version von StateFlow mit einer offengelegten value-Eigenschaft zum Schreiben. Ähnlich wie MutableLiveData wird MutableStateFlow innerhalb von ViewModel verwendet und als StateFlow (schreibgeschützt) für externe Abonnenten veröffentlicht.
class TimerViewModel : ViewModel() {
private val _seconds = MutableStateFlow(0)
val seconds: StateFlow<Int> get() = _seconds
private val _isRunning = MutableStateFlow(false)
val isRunning: StateFlow<Boolean> get() = _isRunning
private var job: Job? = null
fun start() {
if (_isRunning.value) return
_isRunning.value = true
job = viewModelScope.launch {
while (_isRunning.value) {
delay(1000)
_seconds.value++
}
}
}
fun stop() {
_isRunning.value = false
job?.cancel()
}
}
Eigenschaften von MutableStateFlow: (1) Der Wert ist immer nicht-null — erfordert Initialisierung über den Konstruktor; (2) Vergleich von alten und neuen Werten über equals() — wenn der neue Wert gleich dem alten ist, werden Abonnenten NICHT benachrichtigt; (3) Schreiben in value ist von jedem Thread aus möglich, blockiert den aufrufenden Thread jedoch nur kurz für die CAS-Operation. Laut der Kotlin Coroutines-Dokumentation reduziert der Vergleich über equals() unnötige Benachrichtigungen um 90% im Vergleich zu LiveData — dies bringt einen Leistungsgewinn bei hohen Aktualisierungsfrequenzen.
Bei der Verwendung von StateFlow in ViewModel befolgen Sie diese Regeln: (1) Verwenden Sie MutableStateFlow mit private-Modifikator innerhalb von ViewModel; (2) Veröffentlichen Sie schreibgeschütztes StateFlow über get(); (3) Verwenden Sie für komplexe Bildschirme eine versiegelte Klasse als Zustandstyp; (4) Vermeiden Sie das Emittieren eines Werts, der dem aktuellen entspricht (StateFlow macht dies automatisch).
// Empfohlene Bildschirmzustandsstruktur
sealed interface ProfileState {
data object Loading : ProfileState
data class Success(
val name: String,
val email: String,
val avatarUrl: String
) : ProfileState
data class Error(val message: String) : ProfileState
}
class ProfileViewModel : ViewModel() {
private val _state = MutableStateFlow<ProfileState>(ProfileState.Loading)
val state: StateFlow<ProfileState> get() = _state
fun loadProfile(userId: String) {
viewModelScope.launch {
_state.value = ProfileState.Loading
try {
val profile = repository.getProfile(userId)
_state.value = ProfileState.Success(
name = profile.name,
email = profile.email,
avatarUrl = profile.avatarUrl
)
} catch (e: Exception) {
_state.value = ProfileState.Error(e.message ?: "Unknown error")
}
}
}
}
Die Verwendung einer versiegelten Klasse als einzigen Zustandstyp ist der von Google empfohlene Ansatz (UDF — Unidirectional Data Flow). Er garantiert, dass sich die UI immer in einem konsistenten Zustand befindet: Loading, Success oder Error, aber nicht gleichzeitig. Bei IT Sectr sind wir 2022 für alle Bildschirme auf StateFlow + versiegelte Klasse umgestiegen — dies hat das Testen von ViewModel dank vorhersagbarer Zustände um 40% vereinfacht.
stateIn() ist ein Operator, der einen kalten Flow in einen heißen StateFlow umwandelt. Er erfordert die Angabe eines CoroutineScope (wo die interne Koroutine läuft) und einer SharingStarted-Strategie. Die richtige Wahl von SharingStarted beeinflusst maßgeblich die Leistung und den Lebenszyklus des StateFlow.
// Drei SharingStarted-Strategien:
// 1. SharingStarted.Eagerly — startet sofort, stoppt nie
val eagerFlow = coldFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = 0
)
// 2. SharingStarted.Lazily — startet beim ersten Abonnenten, stoppt nie
val lazyFlow = coldFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = 0
)
// 3. SharingStarted.WhileSubscribed() — startet bei vorhandenen Abonnenten,
// stoppt nach stopTimeoutMillis (Standard 0) nach Verlassen des letzten Abonnenten
val whileSubscribedFlow = coldFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
initialValue = 0
)
WhileSubscribed(5000) — die optimale Strategie für ViewModel: Nachdem der letzte Abonnent gegangen ist, arbeitet die interne Koroutine für weitere 5 Sekunden weiter. Wenn der Benutzer innerhalb dieser Zeit zum Bildschirm zurückkehrt, wird das Abonnement ohne Neustart des Flows wiederhergestellt. Der Timeout verhindert häufige Neustarts beim schnellen Wechsel zwischen Bildschirmen. Laut Google-Tests (Android Performance, 2024) reduziert WhileSubscribed mit einem 5-Sekunden-Timeout den CPU-Verbrauch um 25% im Vergleich zu Eagerly.
Ein vollwertiger Suchbildschirm mit Suchanfrage, Ergebnissen und Ladezustand. Das ViewModel verwendet eine versiegelte Klasse UIState und StateFlow für die reaktive Kommunikation mit Compose.
sealed interface SearchUiState {
data object Empty : SearchUiState
data object Loading : SearchUiState
data class Results(val items: List<Product>) : SearchUiState
data class Error(val message: String) : SearchUiState
}
class SearchViewModel constructor(
private val repository: ProductRepository
) : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> get() = _searchQuery
private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Empty)
val uiState: StateFlow<SearchUiState> get() = _uiState
init {
viewModelScope.launch {
_searchQuery
.debounce(300)
.filter { it.length >= 3 }
.flatMapLatest { query ->
_uiState.value = SearchUiState.Loading
repository.searchProducts(query)
}
.collect { products ->
_uiState.value = SearchUiState.Results(products)
}
}
}
fun onQueryChanged(query: String) {
_searchQuery.value = query
}
}
// In Compose:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsState()
// ... UI, das auf Loading-, Results-, Error-Zustände reagiert
}
Room (seit Version 2.4.0) unterstützt die Rückgabe von Flow aus DAO. Das Kombinieren mehrerer Flows über combine ist ein leistungsstarkes Muster für komplexe Bildschirme.
@Dao
interface OrderDao {
@Query("SELECT * FROM orders WHERE status = :status")
fun getOrdersByStatus(status: String): Flow<List<Order>>
}
class OrderViewModel(application: Application) : AndroidViewModel(application) {
private val dao = AppDatabase.getDatabase(application).orderDao()
val activeOrders: StateFlow<List<Order>> = dao.getOrdersByStatus("active")
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
val summary: StateFlow<OrderSummary> = combine(
dao.getOrdersByStatus("active"),
dao.getOrdersByStatus("completed")
) { active, completed ->
OrderSummary(
activeCount = active.size,
completedCount = completed.size,
totalAmount = (active + completed).sumOf { it.amount }
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), OrderSummary(0, 0, 0.0))
}
Room verfolgt automatisch Änderungen in den orders-Tabellen und fragt Daten bei jeder Änderung neu ab. StateFlow + Room ist der moderne Ersatz für Room + LiveData. Laut Google (Android Architecture Guide, 2025) wird der Stack Flow + StateFlow + Room für alle Kotlin-Projekte empfohlen, die reaktive UI-Updates bei Datenbankänderungen benötigen.
Häufig gestellte Fragen
Konflation ist ein Mechanismus, bei dem StateFlow nur den zuletzt gesendeten Wert behält. Wenn ein neuer Wert gesendet wird, bevor der Abonnent den vorherigen verarbeitet hat, geht der Zwischenwert verloren. Dies ist wichtig für die UI: Wenn sich der Zustand von Loading → Success → Error ändert und die UI Success nicht gerendert hat, wechselt sie direkt zu Error ohne zusätzliches Rendering. Konflation ist eine wichtige Android-Optimierung, die übermäßige Neuzusammenstellungen in Compose verhindert.
Verwenden Sie die Erweiterungsfunktion liveData.asFlow() aus der Bibliothek lifecycle-livedata-ktx, dann .stateIn() zur Konvertierung in StateFlow. Die Rückkonvertierung ist stateFlow.asLiveData(). Die Konvertierung ist bei der Migration von LiveData zu StateFlow nützlich: Sie können ViewModels schrittweise auf StateFlow umstellen, während die alte View über LiveData abonniert bleibt.
StateFlow muss immer einen Wert haben — dies ist der Interface-Vertrag: Jeder neu verbundene Abonnent erhält sofort den aktuellen Zustand ohne Wartezeit. Der Anfangswert wird an den Konstruktor MutableStateFlow(initialValue) oder an den Operator stateIn(initialValue) übergeben. Wenn der Zustand nicht vorhanden sein kann, verwenden Sie MutableStateFlow<T?>(null) mit einem nullfähigen Typ und behandeln Sie null in der UI.
Ja, StateFlow ist threadsicher: Das Lesen und Schreiben von value verwendet atomare Operationen (CAS). Allerdings ist collect() eine suspend-Funktion und muss in einer Koroutine gestartet werden. Wenn Emission und Sammlung auf verschiedenen Threads stattfinden, garantiert StateFlow happens-before für alle Operationen auf value. Zum Sammeln von StateFlow in der View verwenden Sie lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } }.
Es gibt keine strenge Grenze, aber es wird empfohlen, nicht mehr als 3-5 separate StateFlows pro Bildschirm zu verwenden. Wenn mehr verschiedene Zustände benötigt werden, kombinieren Sie sie über eine versiegelte Klasse oder data class zu einem. Jeder StateFlow erfordert die Zuweisung eines Continuation-Objekts während der Sammlung — hundert StateFlows können eine merkliche GC-Last verursachen. Laut Google-Empfehlung ist eine versiegelte Klasse UIState pro Bildschirm der optimale Kompromiss zwischen Lesbarkeit und Leistung.
Zusammenfassung
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.
Lesen Sie auch