UI-State als sealed interface – Loading, Success, Error sauber modellieren
Zwei Booleans für "lädt" und "Fehler"? Das gibt unmögliche Zustände. Mit einem sealed interface bildest du genau die Fälle ab, die es wirklich gibt – und der Compiler hilft dir.
Viele Screens tragen Zustände wie isLoading, error und data als einzelne Felder mit sich herum. Das Problem: Damit sind unmögliche Kombinationen möglich – „lädt gerade" und „Fehler" und „Daten da" gleichzeitig. Mit einem sealed interface modellierst du stattdessen genau die Fälle, die es wirklich gibt.
Der Zustand als abgeschlossene Menge
sealed interface UiState {
data object Loading : UiState
data class Success(val user: User) : UiState
data class Error(val message: String) : UiState
}„Sealed" heißt: Alle möglichen Ausprägungen stehen in dieser Datei – niemand kann von außen einen vierten, unerwarteten Zustand hinzufügen. Genau das nutzt der Compiler gleich aus.
Im ViewModel
private val _state = MutableStateFlow<UiState>(UiState.Loading)
val state: StateFlow<UiState> = _state.asStateFlow()
fun load(id: String) {
viewModelScope.launch {
_state.value = UiState.Loading
_state.value = runCatching { repo.user(id) }
.fold(
onSuccess = { UiState.Success(it) },
onFailure = { UiState.Error(it.message ?: "Unbekannter Fehler") }
)
}
}In Compose – der Compiler zwingt dich zur Vollständigkeit
when (val s = state) {
UiState.Loading -> CircularProgressIndicator()
is UiState.Success -> UserCard(s.user) // s.user ist hier typsicher verfügbar
is UiState.Error -> ErrorBanner(s.message)
}Weil UiState sealed ist, ist dieses when erschöpfend – vergisst du einen Fall, gibt es einen Compile-Fehler statt eines Bugs zur Laufzeit. Und dank Smart-Cast greifst du in jedem Zweig typsicher auf genau die Daten zu, die es dort gibt (s.user, s.message).
Warum sich das lohnt
- Keine unmöglichen Zustände mehr – jeder Zustand ist genau einer der drei Fälle.
- Compiler als Sicherheitsnetz: Neue Fälle erzwingen, dass du alle
whenanpasst. - Lesbar: Der Typ dokumentiert selbst, was passieren kann.
Ich baue Android-Apps mit sauberer State-Architektur in Kotlin & Compose – sprich mich an.
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
Room-Migrationen – das Schema ändern, ohne Nutzerdaten zu verlieren
Eine neue Spalte in der Entity, und beim nächsten Start ist der Spielstand weg – weil fallbackToDestructiveMigration die Datenbank einfach löscht. So machst du es richtig, inklusive Test.
stateIn und shareIn – aus einem kalten Flow wird geteilter Zustand
Jeder Sammler eines kalten Flows startet die Arbeit neu – zwei Beobachter, zwei Datenbankabfragen. stateIn und shareIn machen daraus einen Strom, den sich alle teilen, samt aktuellem Wert und Abschalten bei Inaktivität.
viewModelScope – Coroutinen, die mit dem Bildschirm verschwinden
Eine Coroutine, die nach dem Schließen des Bildschirms weiterläuft, schreibt in ein ViewModel, das niemand mehr sieht – und hält im schlimmsten Fall die ganze Activity im Speicher. viewModelScope beendet sie automatisch.