Parcelerとは
例えばActivityから別のActivityに自分で作ったクラスのオブジェクトを渡したりするとき、そのクラスにParcelableを実装する必要がありますが、それが結構面倒で、かつクラスの要素を変更したら、Parcelableの実装もきちんと追従しないとオブジェクトの中身が適切に渡せなかったりします(Androidで昔からある機能なので、詳細は検索したり、こちらを参照してください)。
そのParcelableを簡単に実装できるライブラリがParcelerです。
ちなみに、似たようなものでPaperParcelというライブラリもありますが、今回はParcelerの話をします。また、旧アノテーションプロセッサを用いた方法となります。
kapt {
generateStubs = true
}
Kotlinで書くときの問題
以下の記事に書いてある問題があり、その解決通りに変更すれば動きます。
要はkotlinのdata classを作っても、引数なしコンストラクタが必要なのでParcelerのためにそれを定義してやる必要があるということです。そしてこれは、Parcelerのために書かれていることが分かりにくかったり、privateにしても、そのdata classをプロパティに持つ別のdata classがあったら引数なしコンストラクタをpublicにせざるを得ないという問題がありました。
@Parcel
data class Foo(
val id: Int,
val hoge: String,
val huga: List<String>
) {
constructor() : this(0, "", emptyListOf()) // Barクラスの引数なしコンストラクタで使いたいのでprivateにできない
}
@Parcel
data class Bar(
val hoge: Foo,
val fuga: String,
val piyo: List<String>
) {
private constructor() : this(Foo(), "", emptyListOf()) // FooはpublicでBarはprivateという違和感
}
引数なしコンストラクタからthisに適当な値をそれぞれ入れてやるのも面倒です(よく忘れたり、順番変えておかしくなる)。
また、引数なしコンストラクタの代わりにデフォルト値をもたせてもいいのですが、不要なときはなるべくもたせたくありません。
ParcelConstructorとParcelPropertyを使う
Parcelerのドキュメントに書かれている@ParcelConstructor
と@ParcelProperty
をつか合うことで回避することができました。
@Parcel
data class Foo @ParcelConstructor constructor(
@ParcelProperty("id") val id: Int,
@ParcelProperty("hoge") val hoge: String,
@ParcelProperty("fuga") val fuga: List<String>
)
プライマリコンストラクタに@ParcelConstructor
をつけて、初期化時にこのコンストラクタを使うようにします。また、各プロパティに@ParcelProperty(:name)
をつけてあげましょう。
これならプライマリコンストラクタしか必要ないし、デフォルト値も不要で、Parcelerに関係するものはアノテーションで明示されているので、分かりやすくなったのではないでしょうか。
独自のParcelConverterを使っていた場合
ThreeTenABPのZonedDateTimeをParcelerで扱うために、独自のConverterを書いていたのですが、これがうまく組み合わせられませんでした。具体的には、実行時にエラーが発生します。
@Parcel
data class Foo @ParcelConstructor constructor(
@ParcelProperty("id") val id: Int,
@field:ParcelPropertyConverter(ZonedDateTimeParcelConverter::class)
@ParcelProperty("hoge") val hoge: ZonedDateTime
)
ということで、module単位でParcelConverterを定義する方法で回避しました(ParcelConverterが一つしかないなら、@ParcelClass
でもいいです)。
@ParcelClasses(
ParcelClass(value = ZonedDateTime::class, annotation = Parcel(converter = ZonedDateTimeParcelConverter::class)),
...
)
class App : Application() {
...
}
間違いや改善案等ありましたら、優しくご指摘いただけると嬉しいです。