Let's think about it this way for a second
A network-based app needs to handle three states in the UI — loading (fetching data), success (data received), and error (network failed) — the user should always know 'what's going on' (you can't just leave a blank screen). The pattern `sealed class UiState { object Loading; data class Success(val data: WeatherResponse); data class Error(val message: String) }` is commonly used for managing this kind of state.
Let's connect this to a real scenario
Type a city name into the TextField and click Search — the ViewModel sets `uiState = UiState.Loading` and calls `weatherApi.getWeather(city)` inside a coroutine. On success it moves to `UiState.Success(response)`; on failure (network down, city not found) it moves to `UiState.Error("...")`. The UI then renders differently based on state: `when (uiState) { is Loading -> CircularProgressIndicator(); is Success -> WeatherCard(...); is Error -> ErrorMessage(...) }`.
Let's look at it together
sealed class WeatherUiState {
object Loading : WeatherUiState()
data class Success(val data: WeatherResponse) : WeatherUiState()
data class Error(val message: String) : WeatherUiState()
}
class WeatherViewModel @Inject constructor(
private val api: WeatherApi
) : ViewModel() {
var uiState by mutableStateOf<WeatherUiState>(WeatherUiState.Loading)
private set
fun search(city: String) {
uiState = WeatherUiState.Loading
viewModelScope.launch {
uiState = try {
WeatherUiState.Success(api.getWeather(city))
} catch (e: Exception) {
WeatherUiState.Error("Could not load weather for $city")
}
}
}
}Type in a city name and search, and you should see a loading spinner followed by the weather data (or an error message) on screen.Try it in 5 minutes
Build the Weather app yourself (search TextField + Loading/Success/Error UI) — try typing a wrong city name to trigger the error state.
One thing to watch out for
Watch out for API rate limits — free-tier weather APIs usually cap requests per day, and testing repeatedly can burn through that limit fast.