🔒 Sealed Class
🎯 Mục tiêu: Hiểu Sealed Class - restricted class hierarchy cho type-safe pattern matching, với các use cases phổ biến như Result và UI State.
💡 Sealed Class là gì?
Phần tiêu đề “💡 Sealed Class là gì?”Sealed class giới hạn hierarchy - compiler biết tất cả subclasses tại compile time. Điều này cho phép exhaustive when (không cần else).
sealed class Result { data class Success(val data: String) : Result() data class Error(val message: String) : Result() data object Loading : Result()}
fun handleResult(result: Result): String = when (result) { is Result.Success -> "Data: ${result.data}" is Result.Error -> "Error: ${result.message}" Result.Loading -> "Loading..." // ✅ Không cần else - compiler biết đã cover hết!}📝 Tại sao cần Sealed Class?
Phần tiêu đề “📝 Tại sao cần Sealed Class?”Vấn đề với Open Classes
Phần tiêu đề “Vấn đề với Open Classes”// ❌ Với open class, bất kỳ ai cũng có thể extendopen class Responseclass Success : Response()class Error : Response()
fun handle(response: Response) = when (response) { is Success -> "OK" is Error -> "Failed" else -> "???" // Phải có else vì có thể có subclass khác!}
// Ai đó ở module khác có thể:class HackedResponse : Response() // Compiler không biết!Giải pháp: Sealed Class
Phần tiêu đề “Giải pháp: Sealed Class”// ✅ Sealed class - chỉ subclasses trong cùng file/modulesealed class Response { data class Success(val data: String) : Response() data class Error(val code: Int, val message: String) : Response() data object Loading : Response()}
fun handle(response: Response) = when (response) { is Response.Success -> "OK: ${response.data}" is Response.Error -> "Failed [${response.code}]: ${response.message}" Response.Loading -> "Loading..." // ✅ Không cần else - compiler đảm bảo exhaustive!}🔧 Cú pháp và Variants
Phần tiêu đề “🔧 Cú pháp và Variants”Subclasses có thể là:
Phần tiêu đề “Subclasses có thể là:”sealed class Event { // data class - có data data class Click(val x: Int, val y: Int) : Event()
// data object - singleton, không có data (Kotlin 1.9+) data object Cancel : Event()
// object - singleton (trước Kotlin 1.9) object Timeout : Event()
// class - có thể mutable class LongPress(var duration: Long) : Event()}Nested vs Separate Classes
Phần tiêu đề “Nested vs Separate Classes”// Option 1: Nested (recommended for related types)sealed class Result { data class Success(val value: Int) : Result() data class Failure(val error: Throwable) : Result()}
// Option 2: Separate (same file or module in Kotlin 1.5+)sealed class NetworkResult
data class NetworkSuccess(val data: String) : NetworkResult()data class NetworkError(val code: Int) : NetworkResult()object NetworkLoading : NetworkResult()🎯 Sealed Class vs Enum
Phần tiêu đề “🎯 Sealed Class vs Enum”| Feature | Sealed Class | Enum |
|---|---|---|
| State riêng | ✅ Mỗi subclass có data riêng | ❌ Cùng structure |
| Multiple instances | ✅ Có thể tạo nhiều instances | ❌ Singleton per value |
| Subclass types | class, data class, object | Chỉ constants |
| Exhaustive when | ✅ Có | ✅ Có |
| Use case | Complex states với data | Simple finite set |
// ❌ Enum không phù hợp - mỗi case cần data khác nhauenum class BadResult { SUCCESS, // Cần data? ERROR, // Cần message và code? LOADING // Không cần gì}
// ✅ Sealed class - mỗi case có structure riêngsealed class GoodResult<out T> { data class Success<T>(val data: T) : GoodResult<T>() data class Error(val code: Int, val message: String) : GoodResult<Nothing>() data object Loading : GoodResult<Nothing>()}📱 Use Case: UI State
Phần tiêu đề “📱 Use Case: UI State”Pattern phổ biến trong Android/Compose:
sealed class UiState<out T> { data object Loading : UiState<Nothing>() data class Success<T>(val data: T) : UiState<T>() data class Error(val message: String) : UiState<Nothing>() data object Empty : UiState<Nothing>()}
// ViewModelclass UserViewModel : ViewModel() { private val _state = MutableStateFlow<UiState<User>>(UiState.Loading) val state: StateFlow<UiState<User>> = _state.asStateFlow()
fun loadUser(id: Long) { viewModelScope.launch { _state.value = UiState.Loading
try { val user = repository.getUser(id) if (user != null) { _state.value = UiState.Success(user) } else { _state.value = UiState.Empty } } catch (e: Exception) { _state.value = UiState.Error(e.message ?: "Unknown error") } } }}
// Composable UI@Composablefun UserScreen(viewModel: UserViewModel) { val state by viewModel.state.collectAsState()
when (state) { is UiState.Loading -> LoadingSpinner() is UiState.Success -> UserProfile((state as UiState.Success<User>).data) is UiState.Error -> ErrorMessage((state as UiState.Error).message) is UiState.Empty -> EmptyState("No user found") }}🔄 Use Case: Result Wrapper
Phần tiêu đề “🔄 Use Case: Result Wrapper”sealed class Result<out T> { data class Success<T>(val value: T) : Result<T>() data class Failure(val error: Throwable) : Result<Nothing>()
// Utility methods inline fun onSuccess(action: (T) -> Unit): Result<T> { if (this is Success) action(value) return this }
inline fun onFailure(action: (Throwable) -> Unit): Result<T> { if (this is Failure) action(error) return this }
fun getOrNull(): T? = (this as? Success)?.value
fun getOrElse(default: @UnsafeVariance T): T = getOrNull() ?: default
inline fun <R> map(transform: (T) -> R): Result<R> = when (this) { is Success -> Success(transform(value)) is Failure -> this }}
// Usagefun fetchUser(id: Long): Result<User> { return try { val user = api.getUser(id) Result.Success(user) } catch (e: Exception) { Result.Failure(e) }}
fun main() { fetchUser(123) .onSuccess { user -> println("Got user: ${user.name}") } .onFailure { error -> println("Error: ${error.message}") } .map { user -> user.email } .getOrElse("unknown@email.com")}🔧 Sealed Interface (Kotlin 1.5+)
Phần tiêu đề “🔧 Sealed Interface (Kotlin 1.5+)”sealed interface Error { val message: String
data class NetworkError(override val message: String, val code: Int) : Error data class DatabaseError(override val message: String, val query: String) : Error data class ValidationError(override val message: String, val field: String) : Error}
// Class có thể implement nhiều sealed interfacessealed interface Recoverablesealed interface Loggable
data class RetryableError( override val message: String, val retryCount: Int) : Error, Recoverable
fun handleError(error: Error) = when (error) { is Error.NetworkError -> "Network issue: ${error.code}" is Error.DatabaseError -> "DB error in query: ${error.query}" is Error.ValidationError -> "Invalid ${error.field}" is RetryableError -> "Will retry (${error.retryCount})"}🛠️ Thực hành
Phần tiêu đề “🛠️ Thực hành”Bài tập: Tạo Payment sealed class
// TODO: Tạo sealed class Payment với:// - Pending(amount: Double)// - Processing(transactionId: String)// - Completed(transactionId: String, timestamp: Long)// - Failed(reason: String, canRetry: Boolean)// - Refunded(originalTransactionId: String, refundAmount: Double)Lời giải:
sealed class Payment { abstract val amount: Double
data class Pending(override val amount: Double) : Payment()
data class Processing( override val amount: Double, val transactionId: String ) : Payment()
data class Completed( override val amount: Double, val transactionId: String, val timestamp: Long = System.currentTimeMillis() ) : Payment()
data class Failed( override val amount: Double, val reason: String, val canRetry: Boolean ) : Payment()
data class Refunded( override val amount: Double, val originalTransactionId: String, val refundAmount: Double ) : Payment()
fun statusDescription(): String = when (this) { is Pending -> "💳 Waiting for payment of $$amount" is Processing -> "⏳ Processing transaction: $transactionId" is Completed -> "✅ Payment completed: $transactionId" is Failed -> "❌ Failed: $reason" + if (canRetry) " (can retry)" else "" is Refunded -> "↩️ Refunded $$refundAmount" }}
fun main() { val payments = listOf( Payment.Pending(100.0), Payment.Processing(100.0, "TXN-001"), Payment.Completed(100.0, "TXN-001"), Payment.Failed(50.0, "Insufficient funds", canRetry = true), Payment.Refunded(75.0, "TXN-002", 75.0) )
payments.forEach { println(it.statusDescription()) }}⚠️ Lưu ý quan trọng
Phần tiêu đề “⚠️ Lưu ý quan trọng”✅ Checklist - Tự kiểm tra
Phần tiêu đề “✅ Checklist - Tự kiểm tra”Sau bài học này, bạn có thể:
- Hiểu tại sao sealed class hữu ích (exhaustive when, type safety)
- Tạo sealed class với data class, object, và class subclasses
- Sử dụng sealed class trong
whenexpression - Phân biệt sealed class vs enum
- Áp dụng UiState pattern
- Tạo Result/Either pattern với sealed class
- Sử dụng sealed interface (Kotlin 1.5+)
Tiếp theo: Inheritance