Careeroza
Modelling UI State
Loading, Success, Error
Every screen that loads data is in one of a few states. Modelling them as a sealed type makes impossible states impossible: you cannot be "loading" and "error" at the same time, and the UI must handle every state.
Example
data class Movie(val title: String, val rating: Double)
sealed interface MoviesUiState {
data object Loading : MoviesUiState
data class Success(val movies: List<Movie>) : MoviesUiState
data class Error(val message: String) : MoviesUiState
}
fun render(state: MoviesUiState) {
when (state) {
MoviesUiState.Loading -> println("[spinner] Loading movies...")
is MoviesUiState.Success ->
if (state.movies.isEmpty()) println("No movies found")
else state.movies.forEach { println("* ${it.title} (${it.rating})") }
is MoviesUiState.Error -> println("Error: ${state.message} [Retry]")
}
}
fun main() {
render(MoviesUiState.Loading)
render(MoviesUiState.Success(listOf(Movie("RRR", 7.8), Movie("Jawan", 7.0))))
render(MoviesUiState.Error("No internet connection"))
}Output
[spinner] Loading movies... * RRR (7.8) * Jawan (7.0) Error: No internet connection [Retry]
Why Not Booleans?
// Fragile: what does isLoading = true AND error != null mean?
data class BadState(val isLoading: Boolean, val movies: List<Movie>?, val error: String?)
// Clear: exactly one state at a time
sealed interface MoviesUiState { /* Loading, Success, Error */ }