はじめに
ながすなりです。withConverter使ってねぇやついる?ちょっと前の俺です...
みなさん、Firestore って使ったことないわけないですね。 Google が提供する NoSQL ドキュメント指向データベースで、リアルタイムにデータを更新できちゃう便利なやつです。詳細な説明は他の記事に預けます。
便利な反面、厄介な点があるとすれば、ドキュメントを取得するときには基本Map形式でデータが返ってくるところでしょうか。もちろん Map であること自体は分かりやすいんですが、いつどこでキーのタイプミスや値の型ズレが起きるか分からないというリスクが付きまといます。
そこで登場するのがwithConverterでございます!!知ってました? ドキュメントを受け取るときに、あらかじめ指定した Dart のクラス(いわゆるモデルクラス)に自動変換してくれる機能が標準で用意されているんです。
これを使うと、いちいちmap['name']
とか書いて「おや? String だと思ったら null が返ってきたぞ?」とか頭を抱える機会が激減します。私自身、これを知る前は「fromJson / toJson を都度書けばいいじゃん」と思いながら細々と開発していたのですが、いざ withConverter に触れてみると、その便利さたるや筆舌に尽くしがたいものがあります。
初めて知ったときは早く教えてくれよ...と、見たこともあったこともそもそも存在すらしてないプログラミングの師匠に半ギレしてました。
本記事では、そんな withConverter を使った Firestore のドキュメント変換について解説していきます。気軽な読み物としてどうぞ!私の屍を超えていってください。
そもそもwithConverter is 何?
端的に言ってしまえばwithConverter は、Firestore ドキュメントの
「Map(JSON 形式)⇔ モデルクラス(Dart のクラス)」
を変換してくれる機能です。
Firestore のドキュメントは基本的に Map 形式で取得されますが、そのままマップを使うのってやっぱり面倒ですよね。しかも、型安全じゃないので、Key のミスや型のズレが起きたらすぐに実行時エラーが飛んできます。恐怖。
そこで withConverter を使うと、データの変換を行うロジックをあらかじめ用意し、コレクションやドキュメント取得の際に Dart のモデルクラスとして受け取れるようになるんです!これが結構便利!
もちろんFromJsonとか使って泥臭くやってもいいんですがめんどくせぇんですよね。ところがwithConverter使えばすっきりですわ。では見ていきましょ!
まず使わないパターンを見よう
import 'package:cloud_firestore/cloud_firestore.dart';
class User {
final String name;
final int age;
User({
required this.name,
required this.age,
});
factory User.fromMap(Map<String, dynamic> map) {
return User(
name: map['name'] as String,
age: map['age'] as int,
);
}
Map<String, dynamic> toMap() {
return {
'name': name,
'age': age,
};
}
}
// データ取得
Future<List<User>> fetchUsers() async {
final snapshot = await FirebaseFirestore.instance
.collection('users')
.get();
// 取得した QuerySnapshot をループで回し、Map を Dart クラスに変換
return snapshot.docs.map((doc) {
final map = doc.data();
return User.fromMap(map);
}).toList();
}
chatGPTちゃんに作ってもらった例なので動くかどうかはわからんですが、でも見た感じ動くと思います。
これでもまぁいいっちゃいいんですが、mapで回しているのがどうにもモサいっすよね。あーあーコードでコネコネしないでいい感じに、なんかきれいにやってくれねぇかなぁ...という願いが叶うのがwithConverterです!!!
使うと...?
公式サイトから引っ張ってきました。
final city = City(
name: "Los Angeles",
state: "CA",
country: "USA",
capital: false,
population: 5000000,
regions: ["west_coast", "socal"],
);
final docRef = db
.collection("cities")
.withConverter(
fromFirestore: City.fromFirestore,
toFirestore: (City city, options) => city.toFirestore(),
)
.doc("LA");
await docRef.set(city);
もうdocRefでwithConverterを定義してあるので特に追加の操作なくsetが書ける。これ公式サイトだと一つしかデータ入れてませんが、これ複数個あったとしても同じコードで記述できます!getなど他の操作も最初の定義以外の何もせずにわりかし型安全に書けるんですね!便利!
これfromJsonとかtoJsonを使ったしたらsetやgetのたびにmapしてjsonに戻したりjsonにして...という作業が発生しますがそれがない!!!さいこう!!!
逆につらみは?
firestoreは結構自由に値が入れられるdbです。なので稀にコードの方で型をちゃんとしていても、dbのほうが型を適当に入れていて、エラーが起こりまくるという事態が起こる可能性があります。
もちろん。DBにそんなものを入れているのが悪いですし、ある意味で健全なエラーな気もするのですが、firestoreの柔軟性を損なわせる行為もして微妙な気分です。ですが型が正しいほうが正しいので、このつらみはきっと間違っているつらみだとは思います。ただ自分が面倒なことがあったので、伝えておきます。
メリット
型安全
コレに尽きますね!
あとはコード記述量が減ってすっきりするのも個人的には推しポイントです。
まとめ
- Firestore はスキーマレスな NoSQL ドキュメント DB。柔軟性が高い反面、何でも Map でやりとりするので、型ミスやデータ不整合が起こりやすい。
- withConverter を使うと、Firestore のデータを Dart のモデルクラスに安全に変換できるようになる。コードの重複や型エラーのストレスから解放される。
- Firestore 側で型が崩れているとエラーになるので、バリデーションの仕組みや運用ルールが必要。逆に言えば、エラーで気づけるのはメリットでもある。
ってわけですね。flutter x firestoreで開発をするのならぜひ使っていってほしいメソッドだなぁと思っています。私は今回freezedを使わないで開発をしていたのですが、freezedのfromJsonやtoJsonをwithConverterに渡すという方法もとれると思うのでさらにコードのスリム化がはかれてよいと思います!
東京卍リベンジャーズを見ていないながすなりではありますが、最後はこの言葉で締めたいと思います。
withConverter使ってねぇやついる?いねぇよなぁ!!!
ありがとうございました!いいねとフォローもしてってね。ついでにXも見てってね。