KotlinのSealed Classを学ぶ

いつも何となくSealed Classを使っているので、ちゃんと調べて学習した。
学習すると新たな発見もあるし、知識も深まった。

作成したコードはgithubで公開している。
https://github.com/ikemura23/Android-Kotlin-Lab/tree/sealed_class

# Sealed Class 公式ドキュメント

まず読むべし

[Sealed Classes - Kotlin Programming Language](https://kotlinlang.org/docs/reference/sealed-classes.html)

# Sealedという単語の意味

単語の意味が分からないので調べた

[sealedの意味・使い方｜英辞郎 on the WEB：アルク](https://eow.alc.co.jp/search?q=sealed)

- 密封された
- 封印された
- 知られていない
- 知ることができない

という意味だった

密封されたクラス、って感じになるんだろうか
whenとシールドクラスを組み合わせると便利

# Sealedを使うと何が便利か？

- 状態管理を簡単に表せる
- 状態に値をもたせられる（Enumではできない）
- When式で分岐するとき、すべてのケースをカバーしてくれる （ReturnでSealed Classを返す場合のみ）

# 実際の使い方

画面の状態を表す`ScreenState`をSealed Classで作った

## Sealed Class中身

```kotlin
/**  
 * 画面の状態
 */
 sealed class ScreenState {  
    // エラー  
  object Error : ScreenState()  
  
    // 読み込み中  
  object Loading : ScreenState()  
  
    // データ取得完了  
  data class Data(val someData: SomeData) : ScreenState()  
}  
```

## ViewModelでの使い方

MutableLiveDataでSealed Classの`ScreenState`を保持する。

```kotlin

class MainViewModel : ViewModel() {
    // Sealed Classの状態
    var state = MutableLiveData<ScreenState>()
    // コルーチンのジョブ
    var job = Job()

    // データ読み込み
    fun load() {
        state.value = ScreenState.Loading // ローディング表示
        job = GlobalScope.launch(Dispatchers.Main) {
            try {
                delay(2000L) // ２秒待つ
                // API通信処理
                val response = SomeData(1) // APIレスポンスを仮作成
                state.value = ScreenState.Data(response) // APIレスポンスを表示
            } catch (e: Exception) {
                state.value = ScreenState.Error // エラー
            }
        }
    }
}
```

処理によって`state`に状態をセットする

## Fragmentでの表示の仕方

FragmentではMainViewModelの`state`を監視して、状態変化を検知する。
検知したら、`when (state)`で分岐し、それぞれの状態によって表示を切り替える。

```kotlin
// 変更を検知
viewModel.state.observe(this, Observer<ScreenState> { state ->  
  when (state) {  
        is ScreenState.Loading -> {  
            //ローディング処理  
  Log.d("MainFragment", "Loading")  
        }  
        is ScreenState.Data -> {  
            //データ取得  
  Log.d("MainFragment", state.someData.toString())  
            text.text = state.someData.id.toString()  
        }  
        is ScreenState.Error -> {  
            //エラー処理  
  Log.d("MainFragment", "Error")  
        }  
    }  
})
```

例えば、この例

```kotlin
sealed class Expr {  
    class Num(val value: Int) : Expr()  
    class Sum(val left: Expr, val right: Expr) : Expr()  
}
```

## 全コード
https://github.com/ikemura23/Android-Kotlin-Lab/tree/sealed_class



# When式で分岐するとき、すべてのケースをカバーしてくれる

ReturnでSealed Classを返す場合のみ、Whenの分岐漏れをコンパイラが検知して教えてくれる。

## Sealed Class

Exprクラスは値を返すNumクラスと値を合計して返すSumクラスを持っている。


```kotlin
sealed class Expr {  
    class Num(val value: Int) : Expr()  
    class Sum(val left: Expr, val right: Expr) : Expr()  
    class Sum2(val left: Expr, val right: Expr) : Expr()  // <= このクラスが追加された時
}  
```

SumクラスをコピーしてSum2クラスを生成する。

## When式

Sum2クラスが`when`の分岐に含まれていない場合、コンパイルエラーとなる。

```kotlin
fun eval(e: Expr): Int =  
    when (e) {  // <= ここでコンパイルErrorになる
        is Expr.Num -> e.value  
  is Expr.Sum -> eval(e.left) + eval(e.right)  
    }
```

when箇所のエラーメッセージ

`Error:(16, 5) Kotlin: 'when' expression must be exhaustive, add necessary 'is Sum2' branch or 'else' branch instead`

# 参考リンク

Sealed Classes - Kotlin Programming Language https://kotlinlang.org/docs/reference/sealed-classes.html

Kotlin Sealed Classes — enums with swag – ProAndroidDev https://proandroiddev.com/kotlin-sealed-classes-enums-with-swag-d3c4b799bcd4