MyFamilyTracker
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Sealed classes are more than a fancy enum. Here are the patterns that make them indispensable for Android development: representing results, modeling state machines, handling events, and using sealed interfaces for better composition.
On this page
sealed classEnums are fixed values. Sealed classes are fixed hierarchies where each subtype can carry different data:
// Enum — can't carry different data per case
enum class NetworkResult {
SUCCESS, // Where's the data?
FAILURE // Where's the error?
}
// Sealed class — each case carries what it needs
sealed class NetworkResult<out T> {
data class Success<T>(val data: T) : NetworkResult<T>()
data class Failure(val code: Int, val message: String) : NetworkResult<Nothing>()
object Loading : NetworkResult<Nothing>()
}When you
whenReplace
try/catchsealed class Result<out T> {
data class Success<T>(val value: T) : Result<T>()
data class Error(val exception: Exception, val message: String = exception.message ?: "") : Result<Nothing>()
val isSuccess: Boolean get() = this is Success
val isError: Boolean get() = this is Error
fun getOrNull(): T? = if (this is Success) value else null
fun getOrThrow(): T = when (this) {
is Success -> value
is Error -> throw exception
}
inline fun onSuccess(action: (T) -> Unit): Result<T> {
if (this is Success) action(value)
return this
}
inline fun onError(action: (Exception) -> Unit): Result<T> {
if (this is Error) action(exception)
return this
}
}
// Usage
suspend fun fetchTask(id: String): Result<Task> {
return try {
Result.Success(apiService.getTask(id))
} catch (e: Exception) {
Result.Error(e)
}
}
// In ViewModel
viewModelScope.launch {
fetchTask(taskId)
.onSuccess { task -> _uiState.value = UiState.Success(task) }
.onError { e -> _uiState.value = UiState.Error(e.message ?: "Unknown error") }
}Sealed classes model state machines explicitly. Every valid state is a subtype. Invalid transitions are compile-time errors:
sealed class OrderState {
object Pending : OrderState()
data class Processing(val processorId: String) : OrderState()
data class Shipped(val trackingNumber: String) : OrderState()
data class Delivered(val deliveredAt: Instant) : OrderState()
data class Cancelled(val reason: String) : OrderState()
fun canCancel(): Boolean = this is Pending || this is Processing
fun canShip(): Boolean = this is Processing
}
class OrderStateMachine(initialState: OrderState = OrderState.Pending) {
var state: OrderState = initialState
private set
fun cancel(reason: String): Result<OrderState> {
return if (state.canCancel()) {
state = OrderState.Cancelled(reason)
Result.Success(state)
} else {
Result.Error(IllegalStateException("Cannot cancel order in state $state"))
}
}
fun ship(trackingNumber: String): Result<OrderState> {
return if (state.canShip()) {
state = OrderState.Shipped(trackingNumber)
Result.Success(state)
} else {
Result.Error(IllegalStateException("Cannot ship order in state $state"))
}
}
}sealed interfacesealed interface UiEvent
sealed interface NavigationEvent : UiEvent
sealed interface AnalyticsEvent : UiEvent
data class ShowSnackbar(val message: String) : UiEvent
data class NavigateToDetail(val taskId: String) : NavigationEvent, UiEvent
data class TaskCreated(val taskId: String) : AnalyticsEvent, UiEvent
// Handler that processes only navigation events
fun handleNavigation(event: NavigationEvent) {
when (event) {
is NavigateToDetail -> navController.navigate("tasks/${event.taskId}")
}
}
// Handler that processes all events
fun handleEvent(event: UiEvent) {
when (event) {
is ShowSnackbar -> showSnackbar(event.message)
is NavigateToDetail -> navController.navigate("tasks/${event.taskId}")
is TaskCreated -> analytics.track("task_created", mapOf("id" to event.taskId))
}
}A class implementing a
sealed interfacesealed classThe most powerful property of sealed classes:
whenfun describeState(state: OrderState): String = when (state) {
OrderState.Pending -> "Waiting for payment"
is OrderState.Processing -> "Being processed by ${state.processorId}"
is OrderState.Shipped -> "In transit: ${state.trackingNumber}"
is OrderState.Delivered -> "Delivered at ${state.deliveredAt}"
is OrderState.Cancelled -> "Cancelled: ${state.reason}"
}
// If you add a new OrderState subtype, this won't compile until you handle itThis is the safety property that makes sealed classes valuable for evolving codebases.
Group related states under a parent:
sealed class TaskFilter {
object All : TaskFilter()
sealed class ByStatus : TaskFilter() {
object Active : ByStatus()
object Completed : ByStatus()
}
sealed class ByPriority : TaskFilter() {
object High : ByPriority()
object Medium : ByPriority()
object Low : ByPriority()
}
data class ByTag(val tag: String) : TaskFilter()
}
// Handle at any level
fun applyFilter(tasks: List<Task>, filter: TaskFilter): List<Task> = when (filter) {
TaskFilter.All -> tasks
is TaskFilter.ByStatus -> when (filter) {
TaskFilter.ByStatus.Active -> tasks.filter { !it.completed }
TaskFilter.ByStatus.Completed -> tasks.filter { it.completed }
}
is TaskFilter.ByPriority -> tasks.filter { it.priority == filter.toPriority() }
is TaskFilter.ByTag -> tasks.filter { filter.tag in it.tags }
}Booleanenum classenum classabstract classinterfacewhenSudarshan Chaudhari
AI Systems Builder / Product Engineer
Bangkok, Thailand
Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.
Related Posts
Related Apps
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
Building something? Available for Android dev and QA consulting.
Work with meComments — powered by Giscus
Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.
ReadPrivate dream journal — structured entry capture, pattern tagging, and optional Claude-powered insight generation. All data stays on-device by default.
ReadWorkout tracker — exercise logging with set/rep/weight history, goal progression, and local Room DB persistence. No account, no cloud sync required.
Read