状況説明
Android アプリ開発をしていて、ちょっと困った状況になりました。
すぐに解決したんですが、こんなことがあるのね、と勉強になったのでこれを書いています。
その「ちょっと困ったこと」は Activity 間であるデータをやり取りしようとしていて起こりました。
渡すデータ
Activity 間でデータの受け渡しをするために、次のような sealed class を作成したとします。
(実際のコードとは異なります)
sealed class Answer : Serializable {
object Yes : Answer()
object No : Answer()
data class Other(val text: String) : Answer()
}
呼び出し側
Answer
クラスは Serializable
を実装しているので、 Intent の putExtra() に渡せます。
今回は仮に Yes
を渡すことにします。
val intent = Intent(context, MainActivity::class.java).apply {
putExtra("answer", Answer.Yes)
}
startActivity(intent)
受け取り側
シンプルに受け取って、 when で分岐処理します。
when の分岐は Android Studio で生成するのが楽ですね。
「alt + Enter」→「 Add remaining branches」で全ての分岐を補完してくれます。
val answer = intent.getSerializableExtra("answer") as? Answer
val text = when (answer) {
Answer.Yes -> "はい"
Answer.No -> "いいえ"
is Answer.Other -> "その他(${answer.text})"
null -> "未選択"
}
上記のコードを実行したとき、どのような結果になるでしょうか?
実行結果
上記コードを実行すると、例外が発生します。
kotlin.NoWhenBranchMatchedException
という例外です。
when は全て網羅されているはずなのに何故?と疑問に感じますが、状況が状況なので割とすぐ見当が付きますね。
シリアライズ・デシリアライズされたことで、 Answer.Yes
とは別のインスタンスが生み出されてしまっています。
インスタンスが一致しないので、該当する分岐がなく例外が発生してしまったというわけです。
解決するには
すぐ思いついた方法
最初に行ったのは、 when の分岐の Yes
も No
も is
で比較するようにすることでした。
- Answer.Yes -> "はい"
+ is Answer.Yes -> "はい"
- Answer.No -> "いいえ"
+ is Answer.No -> "いいえ"
この状態であれば、期待通りに動作します。
しかし、 object
で定義された Yes
や No
のインスタンスが複数存在しても良いのか? とスッキリしません。
きちんと同一インスタンスを返すには
スッキリしないので調べてみると、 Serializable なシングルトンオブジェクトを作成するときの実装方法が見つかりました。
readResolve()
というメソッドを定義してあげれば良いようです。
Android の Serializable のページにも書かれていますね(ちゃんと読んでないけど)。
sealed class Answer : Serializable {
object Yes : Answer() { private fun readResolve(): Any = Yes }
object No : Answer() { private fun readResolve(): Any = No }
data class Other(val text: String) : Answer()
}
readResolve()
でデシリアライズ時の処理を上書きできるようなので、ここで Yes
No
を返すことで Serializable を実装していても object のインスタンスを 1つにすることができました。
このように object が定義されていれば、 when の分岐でわざわざ is
を使わなくても大丈夫です
まとめ
object で定義したクラスを Serializable にするときは気をつけようね、という話でした。
調べればすぐわかるようなものなので、ハマることはないと思います。
知らなかったことを知ることができて、楽しかったです。