Added documentation outlining the recommended frontend state-management approach using Unidirectional Data Flow (UDF). Documented the 2026-01-28 session addressing the critical SQLDelight async issue, detailing the analysis, fix implementation, and results. Updated PingEventRepositoryImpl to use `awaitAsOneOrNull` for proper async handling.
4.1 KiB
| type | status | owner | last_update |
|---|---|---|---|
| Reference | DRAFT | Frontend Expert | 2026-01-28 |
Frontend State-Management Strategie (UDF)
Dieses Dokument beschreibt die empfohlene Strategie für das State Management in komplexen UI-Komponenten wie Formularen und Reports, um die Skalierbarkeit und Wartbarkeit des Frontends sicherzustellen.
Problemstellung: Grenzen des einfachen State Managements
Für einfache Screens ist die Verwendung von mehreren StateFlows in einem ViewModel ein valider Ansatz.
// Beispiel für einen einfachen Ansatz
class SimpleViewModel : ViewModel() {
private val _name = MutableStateFlow("")
val name: StateFlow<String> = _name
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading
}
Bei wachsender Komplexität (viele Felder, Validierungsregeln, asynchrone Aktionen) führt dieser Ansatz zu Problemen:
- Inkonsistente Zustände: Es ist leicht, einen State zu vergessen (z.B.
isLoadingauffalsezu setzen, aber die Fehlermeldung nicht zu löschen), was zu schwer nachvollziehbaren UI-Bugs führt. - Schwere Testbarkeit: Die Logik ist über viele Funktionen verteilt, die einzelne State-Variablen mutieren.
- Race Conditions: Mehrere gleichzeitige, asynchrone Updates können den State unvorhersehbar machen.
Lösungsstrategie: Unidirectional Data Flow (UDF)
Um diese Probleme zu lösen, wird die Einführung eines Unidirectional Data Flow (UDF) Musters für alle neuen, komplexen Features empfohlen.
UDF erzwingt einen strikten, vorhersagbaren Kreislauf:
- State: Ein einziges, unveränderliches (immutable)
data classrepräsentiert den gesamten Zustand der UI. Dies ist die Single Source of Truth. - Event/Intent: Die UI ändert den Zustand niemals direkt. Stattdessen sendet sie ein Event (z.B.
SaveButtonClicked), um eine Benutzeraktion zu signalisieren. - Logic (Reducer): Eine zentrale Logik-Komponente (im ViewModel) empfängt das Event, führt Geschäftslogik aus und produziert einen neuen, kompletten State.
Beispiel-Implementierung
1. Ein einziges State-Objekt:
data class FormState(
val name: String = "",
val email: String = "",
val isSaving: Boolean = false,
val saveError: String? = null,
val isFormValid: Boolean = false
)
2. Klare Events von der UI:
sealed interface FormEvent {
data class NameChanged(val newName: String) : FormEvent
object SaveButtonClicked : FormEvent
object ErrorDismissed : FormEvent
}
3. Zentrale Logik im ViewModel:
class FormViewModel : ViewModel() {
private val _state = MutableStateFlow(FormState())
val state: StateFlow<FormState> = _state
fun onEvent(event: FormEvent) {
when (event) {
is FormEvent.NameChanged -> {
_state.update { it.copy(name = event.newName, isFormValid = /*...*/) }
}
is FormEvent.SaveButtonClicked -> {
_state.update { it.copy(isSaving = true) }
// ...
}
}
}
}
Empfohlene Bibliotheken
Die manuelle Implementierung von UDF ist möglich, aber dedizierte Bibliotheken bieten eine bewährte Struktur und reduzieren Boilerplate.
-
Voyager:
- Primär eine Navigationsbibliothek, die aber ein leichtgewichtetes und pragmatisches UDF-System (
ScreenModel) mitbringt. - Empfehlung: Der empfohlene Einstiegspunkt. Da das Projekt ohnehin eine Navigationslösung benötigt, ist dies die effizienteste Wahl.
- Primär eine Navigationsbibliothek, die aber ein leichtgewichtetes und pragmatisches UDF-System (
-
MVIKotlin:
- Eine sehr mächtige, explizite MVI-Bibliothek (eine Form von UDF).
- Empfehlung: In der Hinterhand behalten. Für extrem komplexe Features, bei denen erweiterte Funktionen wie Time-Travel-Debugging den Mehraufwand rechtfertigen, kann MVIKotlin gezielt eingesetzt werden.
Nächste Schritte
Diese Strategie muss nicht sofort für bestehende Screens umgesetzt werden. Sie soll als Blaupause für alle zukünftigen, fachlichen Features dienen, sobald das grundlegende SQLDelight-Sync-Problem gelöst ist.