Lesson 21: Displaying Network Data in a List
Displaying Network Data in a List
Let's finish the networking flow properly, with the three states every network-backed screen actually needs: loading, error, and success.
A sealed class for UI state
sealed class TaskListUiState {
object Loading : TaskListUiState()
data class Success(val tasks: List) : TaskListUiState()
data class Error(val message: String) : TaskListUiState()
}A sealed class represents a fixed set of possible states — Kotlin's compiler knows these are the only three possibilities, which lets it warn you if you forget to handle one.
Updating the ViewModel
class TaskListViewModel : ViewModel() {
var uiState by mutableStateOf(TaskListUiState.Loading)
private set
init {
viewModelScope.launch {
uiState = try {
val tasks = taskApi.getTasks().map { it.toTask() }
TaskListUiState.Success(tasks)
} catch (e: Exception) {
TaskListUiState.Error("Couldn't load tasks. Check your connection.")
}
}
}
}Rendering all three states
@Composable
fun TaskListScreen(viewModel: TaskListViewModel = viewModel()) {
when (val state = viewModel.uiState) {
is TaskListUiState.Loading -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
}
is TaskListUiState.Error -> {
Text(state.message, modifier = Modifier.padding(24.dp))
}
is TaskListUiState.Success -> {
LazyColumn {
items(state.tasks) { task ->
Text(task.title, modifier = Modifier.padding(16.dp))
}
}
}
}
}The when here is exhaustive — Kotlin verifies every branch of the sealed class is handled. This pattern (sealed class UI state + exhaustive when) is one of the most valuable habits in real Android apps: it makes "forgot to handle the error case" a compile-time warning instead of a production bug.
Next: Lesson 22 — saving data locally with a Room database.