LoginSignup
21

More than 3 years have passed since last update.

KotlinのSealed Classを学ぶ

Last updated at Posted at 2018-11-28

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

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

Sealed Class 公式ドキュメント

まず読むべし

Sealed Classes - Kotlin Programming Language

Sealedという単語の意味

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

sealedの意味・使い方|英辞郎 on the WEB:アルク

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

という意味だった

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

Sealedを使うと何が便利か?

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

実際の使い方

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

Sealed Class中身

/**  
 * 画面の状態
 */
 sealed class ScreenState {  
    // エラー  
  object Error : ScreenState()  

    // 読み込み中  
  object Loading : ScreenState()  

    // データ取得完了  
  data class Data(val someData: SomeData) : ScreenState()  
}  

ViewModelでの使い方

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


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) // 2秒待つ
                // 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)で分岐し、それぞれの状態によって表示を切り替える。

// 変更を検知
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")  
        }  
    }  
})

例えば、この例

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

全コード

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

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

Sealed Class

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

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の分岐に含まれていない場合、コンパイルエラーとなる。

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21