エラーが発生した当時の環境
Firestoreに保存されているドキュメントを取得する際に、toObject()
を呼び出してドキュメントをデータクラスのインスタンスに変換しようとしました。
その時に使用したのが、以下の内容のMember
データクラスです。プロパティとして、String
型やDocumentReference
型にTimestamp
型など、Firebaseを使う方にはお馴染みの内容かと思われます。
data class Member(
val id: String = "",
val name: String = "",
val groupRef: DocumentReference,
val imageRef: DocumentReference? = null,
val createdAt: Timestamp = Timestamp(Date()),
)
そして、いつものように取得したDocumentSnapshot
インスタンスの値に対してtoObject()
を呼び出して、定義したデータクラスのインスタンスにデシリアライズを行おうとしました。
val documentReference = firestore.collection("members").document("member-id-1")
val snapshot = documentReference.get().await()
val data = snapshot.toObject<Member>()?.copy(
id = snapshot.id
)
Could not deserialize object...エラーの発生
上記の処理を実行したら、次のようなエラーが発生しました。
E java.lang.RuntimeException: Could not deserialize object. Class com.takagimeow.xxx.core.service.model.Member does not define a no-argument constructor. If you are using ProGuard, make sure these constructors are not stripped
Could not deserialize object...エラーの発生箇所
Log.d()
などを配置して、順を追って調べていくと、なんとエラーの原因がsnapshot.toObject<Member>()
の呼び出しであることが判明しました。
return snapshot.toObject<Member>()?.copy(
id = snapshot.id
)
ただ、問題の原因はこのtoObject<Member>()
の呼び出し事態ではありませんでした。
実は、このMember
データクラスのコンストラクタの定義自体に問題があったのです。
toObject()に渡すデータクラスを定義する際の注意点
コンストラクタの部分をよく見ると、groupRef
プロパティにだけ、初期値が設定されていないことがわかると思います。
data class Member(
...
val groupRef: DocumentReference,
...
)
Kotlinのデータクラスを定義した場合、引数のないコンストラクタは自動的に作成されません。
前提知識として、Android用のFirebase SDKでは、DocumentSnapshot
インスタンスの値に対してtoObject<T>()
を呼び出すことで自動的にデシリアライゼーションが行われます。
そのため、toObject()
に使用するデータクラスの定義時には、引数なしでコンストラクタを呼び出しても大丈夫なように各プロパティに対して初期値を指定してあげる必要があるのです。
このとき、データクラスのすべてのプロパティには、null
もしくは型に対応した適切な値を初期値として渡します。
もし初期値を設定しない場合は、デシリアライゼーションの機能が使えなくなってしまうため、toObject()
呼び出しが失敗します。そして、その失敗の原因としてCould not deserialize object...
エラーが発生することになります。
データクラスのすべてのプロパティに初期値を設定する
修正したMember
データクラスは次のようになります。
data class Member(
val id: String = "",
val name: String = "",
val groupRef: DocumentReference? = null,
val imageRef: DocumentReference? = null,
val createdAt: Timestamp = Timestamp(Date()),
)
この修正のおかげで、無事エラーの発生は解消されました。
まとめ
Android用Firebase SDKを使う際は、データクラスの定義時に必ず初期値を渡すことをこころがけながら開発することを強くおすすめします。さもないと、自分のように解決までに無駄な時間を費やしてしまうことになるかもしれません・・・
参考にした記事