【正社員】還元率83%【フリーランス】マージン一律5万円で案件をご紹介させていただきます。 詳細はこちら

【Android】<Kotlin>sealed classとは?仕組み・使い方・メリットを完全網羅

【Android】<Kotlin>sealed classとは?仕組み・使い方・メリットを完全網羅
すだ

みなさまこんにちは〜!
メモリアインクのすだです。

今回は、sealed classを使って処理を実装する方法について
わかりやすく解説していきます

この記事を読んでわかること…
・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 はジェネリクスに対応しており、どんなデータ型にも使えます。
LoadingError は 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エンジニアを目指すなら【ユニゾンキャリア】
を通じて、
自分の市場価値をさらに向上させてみませんか?

それではまた次回の記事でお会いしましょう!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

弊社テックブログをご愛読いただきありがとうございます。
当テックブログを運用している株式会社メモリアインクは、
【正社員】還元率83%
【フリーランス】マージン一律5万円で案件のご紹介
と、エンジニアの皆様に分かりやすい形で稼げる仕組みを構築し提供させていただいております。

コメント

コメントする

目次