Sealed Classes & when
The most powerful state modeling pattern in Kotlin — when expressions and sealed classes for exhaustive, compile-safe state.
The when Expression
Kotlin's when replaces Java's switch — but it's far more powerful. It can match types, ranges, conditions, and be used as an expression:
String day = "Monday";
String type;
switch (day) {
case "Monday":
case "Tuesday":
case "Wednesday":
case "Thursday":
case "Friday":
type = "Weekday";
break;
case "Saturday":
case "Sunday":
type = "Weekend";
break;
default:
type = "Unknown";
}
// switch is a statement, not an expressionval day = "Monday"
// when IS an expression — can assign!
val type = when (day) {
"Saturday", "Sunday" -> "Weekend"
"Monday", "Tuesday",
"Wednesday", "Thursday",
"Friday" -> "Weekday"
else -> "Unknown"
}
// when with ranges and conditions:
val score = 85
val grade = when {
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
else -> "F"
}when with Type Matching
Object obj = getObject();
if (obj instanceof String) {
System.out.println(((String) obj).length());
} else if (obj instanceof Integer) {
System.out.println((Integer) obj * 2);
} else {
System.out.println("Unknown");
}val obj: Any = getObject()
when (obj) {
is String -> println(obj.length) // smart cast!
is Int -> println(obj * 2) // smart cast!
is Boolean -> println(if(obj) "yes" else "no")
else -> println("Unknown")
}Sealed Classes — Exhaustive State
A sealed class restricts its subclasses to a known set — perfect for modeling UI state or network results. The compiler knows every possible subtype:
// Java enum can't carry data
public enum UiState {
LOADING, SUCCESS, ERROR
}
// OR verbose class hierarchy with instanceof:
if (state instanceof Loading) { ... }
else if (state instanceof Success) {
Success s = (Success) state;
showData(s.getData());
} else if (state instanceof Error) { ... }
// Easy to forget a branch — no compile error!// Sealed class — each subtype can carry data
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<User>) : UiState()
data class Error(val message: String) : UiState()
}
// when is EXHAUSTIVE — compiler checks all branches!
when (val state = uiState.value) {
is UiState.Loading -> showLoading()
is UiState.Success -> showData(state.data)
is UiState.Error -> showError(state.message)
// No 'else' needed — compiler knows all subtypes
// Forget a branch? Compile error! ✅
}when — the killer featureWhen you use when as an expression (assigning its result to a variable), the Kotlin compiler enforces exhaustiveness. Add a new subclass to your sealed class and the compiler immediately flags all unhandled when branches. This is impossible in Java.
Sealed Classes in Android — Real-world Pattern
// Define states
sealed class HomeState {
object Loading : HomeState()
data class Content(val users: List<User>) : HomeState()
data class Error(val msg: String) : HomeState()
object Empty : HomeState()
}
// ViewModel emits states
class HomeViewModel : ViewModel() {
private val _state = MutableStateFlow<HomeState>(HomeState.Loading)
val state = _state.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
_state.value = HomeState.Loading
try {
val users = repository.getUsers()
_state.value = if (users.isEmpty())
HomeState.Empty
else
HomeState.Content(users)
} catch (e: Exception) {
_state.value = HomeState.Error(e.message ?: "Unknown error")
}
}
}
}
// Composable renders each state
@Composable
fun HomeScreen(state: HomeState) {
when (state) {
is HomeState.Loading -> CircularProgressIndicator()
is HomeState.Content -> UserList(state.users)
is HomeState.Error -> ErrorMessage(state.msg)
is HomeState.Empty -> EmptyState()
}
}Sealed Interface (Kotlin 1.5+)
// Sealed interface — subclasses can implement other interfaces
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Failure(val error: Throwable) : Result<Nothing>
object Loading : Result<Nothing>
}
// Generic result type — very common pattern:
suspend fun fetchUser(): Result<User> = try {
Result.Success(api.getUser())
} catch (e: Exception) {
Result.Failure(e)
}Course Complete!
You've completed all 12 chapters of Kotlin for Java Developers. You now know the essential patterns to write production-quality Kotlin for Android.