0
Help us understand the problem. What are the problem?

posted at

Organization

[Kotlin][Android] object と Serializable

状況説明

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」→「:bulb: 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 の分岐の YesNois で比較するようにすることでした。

-    Answer.Yes -> "はい"
+    is Answer.Yes -> "はい"
-    Answer.No -> "いいえ"
+    is Answer.No -> "いいえ"

この状態であれば、期待通りに動作します。
しかし、 object で定義された YesNo のインスタンスが複数存在しても良いのか? :thinking: とスッキリしません。

きちんと同一インスタンスを返すには

スッキリしないので調べてみると、 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 を使わなくても大丈夫です :thumbsup:

まとめ

object で定義したクラスを Serializable にするときは気をつけようね、という話でした。
調べればすぐわかるようなものなので、ハマることはないと思います。
知らなかったことを知ることができて、楽しかったです。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?