
みなさまこんにちは〜!
メモリアインクのすだです。
今回は、
わかりやすく解説していきます
この記事を読んでわかること…
・seald classとは
・seald classの基本的な使い方
・seald classを使った実践的な処理の例
環境
- Kotlin (ver 1.9.0)
- Android Studio (Giraffe | 2022.3.1 Patch 3)
sealed classとは?
sealed class
(シールドクラス)とは、継承できるクラスの種類を制限した抽象クラスの一種です。
つまり、そのクラスを継承できるのは、同じファイル内で定義されたクラスだけに限定されます。
これにより、コンパイラはどんなクラスが存在するかをすべて把握できるため、when
式で安全に分岐処理が行えます。
sealed classを使うメリット
sealed classは以下のようなメリットがあります。
- 安全な分岐処理:全てのサブクラスが明示されているため、when 式で
else
を書かなくてもOK。 - 状態の明確化:状態ごとにクラスを定義することで、アプリの状態管理が明確になる。
- 拡張がしやすい:追加の状態やイベントを簡単に追加できる。
sealed classの基本構文
// ファイル: Status.kt
sealed class Result {
class Success(val data: String) : Result()
class Error(val exception: Exception) : Result()
object Loading : Result()
}
上記の例では、Result
という sealed class を定義し、
それを継承する3つの状態を表すクラス(Success、Error、Loading)を定義しています。
ちなみに、以下のように別ファイルにサブクラスを定義しようとするとエラーになります▼
// ファイル: Status.kt
sealed class Status
// ファイル: Success.kt(←これは別ファイル)
data class Success(val data: String) : Status() // エラーになる
when式と組み合わせた使い方
sealed class を使う際によくセットで使われるのが when
式です。
fun handleResult(result: Result) {
when (result) {
is Success -> println("成功:${result.data}")
is Error -> println("エラー:${result.exception.message}")
is Loading -> println("読み込み中...")
}
}
このように when
式で分岐させると、コンパイラがすべてのサブクラスを網羅していることを確認してくれるので、安全です。
たとえば、データを取得して表示する画面があるとしましょう。そのときの状態を以下のように定義できます。
sealed class UiState {
object Loading : UiState()
data class Success(val data: List<String>) : UiState()
data class Error(val message: String) : UiState()
}
そして、画面側では次のように状態を見て処理します:
when (val state = viewModel.uiState) {
is UiState.Loading -> showLoading()
is UiState.Success -> showData(state.data)
is UiState.Error -> showError(state.message)
}
実践的な活用の例
sealed class を使ったより実践的な処理の例として、
APIレスポンスの状態管理(成功・失敗・読み込み中)を ViewModel から UI に通知するケースを実装してみます。
1. sealed class を定義(APIの状態表現)
sealed class ApiResult<out T> {
object Loading : ApiResult<Nothing>()
data class Success<T>(val data: T) : ApiResult<T>()
data class Error(val message: String) : ApiResult<Nothing>()
}
Success
はジェネリクスに対応しており、どんなデータ型にも使えます。Loading
や Error
は UI 側での状態切り替えに便利です。
→このように、成功・失敗・読み込み中の状態を1つの型で安全に表現できます。
2. ViewModel 側で API を呼び出す
class UserViewModel : ViewModel() {
private val _userState = MutableStateFlow<ApiResult<List<String>>>(ApiResult.Loading)
val userState: StateFlow<ApiResult<List<String>>> = _userState
fun fetchUsers() {
viewModelScope.launch {
_userState.value = ApiResult.Loading
try {
delay(1000) // APIを呼ぶ代わりに疑似待機
val users = listOf("Alice", "Bob", "Charlie") // 疑似データ
_userState.value = ApiResult.Success(users)
} catch (e: Exception) {
_userState.value = ApiResult.Error("ユーザー取得に失敗しました")
}
}
}
}
ここでは、ViewModelで保持するStateを ApiResult<List<String>>
というジェネリクス型の sealed class
を使って状態管理しています。
そして、APIを呼び出す処理の流れの中で
APIを呼ぶ直前で状態をApiResult.Loading
に変更、
APIの結果が正常に返ってきた場合は状態をApiResult.Success(users)
に変更、
APIの結果catchに入った場合には状態をApiResult.Error("ユーザー取得に失敗しました")
に変更しています。
このように、ApiResult
というseald class
を使ってわかりやすい状態管理を実現しています。
3. Composable関数で状態をUIに反映する
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val userState by viewModel.userState.collectAsState()
LaunchedEffect(Unit) {
viewModel.fetchUsers() // ← Compose画面が表示された直後に1回実行
}
when (userState) {
is ApiResult.Loading -> {
CircularProgressIndicator()
}
is ApiResult.Success -> {
val users = (userState as ApiResult.Success).data
LazyColumn {
items(users) { user ->
Text(text = user)
}
}
}
is ApiResult.Error -> {
val message = (userState as ApiResult.Error).message
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = message, color = Color.Red)
Button(onClick = { viewModel.fetchUsers() }) {
Text("リトライ")
}
}
}
}
}
UI側では、userState の状態に応じて、when式を使って画面の表示を切り替えています。
このとき、userStateの型はApiResult
というsealed class
で定義されているため、Loading
/ Success
/ Error
など、すべての可能な状態を網羅的に安全に分岐することができます。
まとめ
おつかれさまでした。いかがでしたでしょうか!
seald classの「すべての状態を1ファイルで把握できる」というのは、小規模でも大規模でも保守性を高めるとても強力なルールです。



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント