たくさんの自由帳
Androidのお話
たくさんの自由帳
投稿日 : | 0 日前
文字数(だいたい) : 5414
どうもこんばんわ
11 月ってこんな暑かったっけ?
Jetpack Compose
を使うときによく、以下のような画面の値を持っておくステートクラスみたいなのを作ると思うんですけど
多分、参考:https://github.com/android/nowinandroid/blob/main/feature/foryou/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/foryou/OnboardingUiState.kt
// HomeScreen の ステート
sealed interface HomeScreenUiState {
// ロード
object Loading : HomeScreenUiState
// 成功
data class Successful(val data: String) : HomeScreenUiState
// 失敗
data class Error(val throwable: Throwable) : HomeScreenUiState
}
private suspend fun getData(): String {
delay(500)
return "Hello World"
}
@Composable
fun HomeScreen() {
val uiState = remember { mutableStateOf<HomeScreenUiState>(HomeScreenUiState.Loading) }
LaunchedEffect(key1 = Unit) {
// 取得処理
val response = getData()
uiState.value = HomeScreenUiState.Successful(response)
}
Box {
when (val state = uiState.value) {
is HomeScreenUiState.Error -> Text(text = "失敗した!")
HomeScreenUiState.Loading -> Text(text = "ロードなう")
is HomeScreenUiState.Successful -> Text(text = "成功 = ${state.data}")
}
}
}
一画面だけなら問題ないと思いますが、似たような画面で使いたい場合に、HomeScreenUiState
の名前変えただけバージョンがコピーされてしまうのは避けたいなと思ったわけで、
解決するにはジェネリクス<T>
みたいなのを使えばいいですね!!
というわけでこれ。
参考:https://stackoverflow.com/questions/44243763/how-to-make-sealed-classes-generic-in-kotlin
<out T>
← out
って何・・・
あと<Nothing>
って何なの?
確かに動くのですが、謎ばっかりだったので調べてみた
シールクラスって言うとかなんとか
TypeScript
だとUnion
が一番近いかな?
このクラスの特徴は継承する際に制限があり(同じパッケージ内で継承して宣言する必要がある?)、その代わり継承しているクラスが分かるという特徴があります。
https://kotlinlang.org/docs/sealed-classes.html#location-of-direct-subclasses
基本的には同じファイル内にすべて継承するクラスを定義するはず。
例えばパッケージが違うとエラー
制限がある代わりに、継承しているクラスが全て分かるため、when
等でinstanceof
する際に継承しているクラスが既にわかっているため、全て網羅すればelse
を追加する必要がないという点です。
これはenum
とかでも言えることですね
例えばUI
になにかアラートか何かを出すため、データを入れておくクラスをつくろうと思います。
アラートはこの三種類、どれも Android 端末を触ったことがあれば出会ったことがあると思う。
汎用的にこんな感じかな
あ!Snackbar
とDialog
はボタンがあるので、ボタンのテキストも設定できるようにしたいですね。
うーん雲行きが怪しく
あとダイアログは外側を押したらキャンセル出来るかのフラグも欲しいかも
きつくなってきた。
アラートの種類はenum
で表現できるのに、種類ごとに必要なデータが違うから使えない・・・!でも継承可だとなんか違う!
ってときに使うといいと思います。
これをうまく使ったのが、冒頭のUiState
ですね
when
の使いやすさと相まっていいと思う
以上!sealed class
ようやく本題、はい。
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/
全てのクラスのスーパークラス
全てのクラスはこのAny
を継承しているってことらしい。
toString
とかはいつ見てもあると思いますがこれは根っこの部分Any
で用意されているからなんですねえ
試しにさっき作ったクラスをインスタンス化しis Any
してみますがもちろんtrue
になります
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-nothing.html
これは逆に全てのクラスを継承したクラスです。
Any
が根っこならNothing
はてっぺんです←?
全てのクラスを継承しているとかいう意味不明なクラスなので、インスタンス化することができません。
存在しない値を表現する際に使うらしい。例えばthrow
した場合Nothing
が返される。
もしNothing
を返す関数があればそれは例外を投げるか、無限ループになって呼び出し元に戻らない?になるらしいです。
イマイチ使い方が思いつかないですが、もう一個、多分こっちが本題!、
インスタンス化して使うことはできませんが、全てのクラスを継承した型として使うことができます。
全てのクラスを継承しているクラス(インスタンス化できないので建前でしか無いですが)はアップキャストと組み合わせることで効果を発揮します。
アップキャストはまぁ調べれば出ると思いますが、子クラスを親クラスにキャストするやつです。あの安全な方のキャスト
Android
だとImageView / TextView (子)
からView (子が継承している親のクラス)
にキャストするみたいな感じです。
話を戻して、アップキャストと組み合わせることでこんな事ができます。いや難しいなこれ
てかこれ考えたん賢くない???頭めっちゃいいと思う
で、これを次の<out T>
と組み合わせることで、Jetpack Compose
のUiState
クラスが共通化できちゃうわけ
ジェネリックなのかジェネリクスなのか、どっちなんだろう
https://kotlinlang.org/docs/generics.html
そこらの言語と同じ奴。
ただ、アップキャストの話をちょっと↑でしましたが、ジェネリクスだとうまく行かないことがあるんですね。(てなことをドキュメントに書いてる)
たとえばこんなコード
String
はAny
を継承しているので、アップキャストにより入れられるはず・・・が入れようとするとエラーになる。継承してるのになぜ?
これは訳あってそうしているらしく、以下のようにダウンキャストの可能性が捨てきれないんですね。(詳しくは Java ジェネリクス 共変
とかで調べてみてください)
参考: https://kotlinlang.org/docs/generics.html#variance
(なので以下のコードは動きません)
話を戻して、で、これを解決する方法があります。
Java
でも出来るらしいですが、Kotlin
だとout
を利用することでこの問題を解決できます。
これだとなんで通るようになるのかですが、<out T>
だとT
は返り値としてしか使うことができず、setter
やvar
等の値を変更する箇所でT
を使おうとすると怒られるわけです。
上記の問題はsetter
を使われてしまった場合に値が変わってしまう可能性があるために出来ないようにしていた、、、
が、変化しないと<out T>
で宣言することでこの問題が解決!ついでに変化しないので継承している子クラスも入れられるようになりました。
こういうステートが作れます。 一体それぞれが何の役割をしているか、わかった気がしませんか?
sealed interface
Loading
/Successful
/Error
の3つに制限when
やin
でクラスを比較する際はこの3つを見ればおっけーobject Loading
object
でいいらしいsealed interface
を継承してねdata class Successful<T>
data class Error
Nothing
Nothing
Nothing
にすることで、アップキャストが働きNothing
以外の型を探そうとし、結果成功時の型が使われるようになる
Successful<T>
の型になる!<out T>
Loading
/Error
の型をどうすればいいの?
Nothing
を使うとアップキャストが効いていい感じ<T>
にout
をつけることで、親クラスを指定した際に子クラスも型パラメータに入れることを許容するように
Nothing
という特殊な型により、自動的に成功時の型が使われるようになる・・・ってことで合ってる?
間違ってたらすいません
難しい
https://stackoverflow.com/questions/44243763/how-to-make-sealed-classes-generic-in-kotlin https://phoneappli.hatenablog.com/entry/2021/03/30/180749 https://stackoverflow.com/questions/55953052/kotlin-void-vs-unit-vs-nothing