いつも何となく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