Flutter x Firebase Firestore (Database) からデータを読み取って使う
私はただの趣味で齧ってるだけなので、本当はもっとたくさん考慮しなきゃいけないのかもしれませんが、、、
自分用総括
変数への格納は以下とするべし
フィールド名: doc.data().containsKey("フィールド名") ? doc["フィールド名"] : "デフォルト値",
これにすれば
・ドキュメントに指定したフィールドがなくても例外が出ない!
・デフォルト値を定義できる!
ので、Firestoreにでたらめなゴミデータがたまっても華麗にスルーできる。
doc["フィールド"] でたまたまそのドキュメントに対象のフィールドが存在しないこともあると思うのですが、それしただけで以下みたいな例外出ちゃうので、、、安全策のために。この安全策を実装するのに30分ぐらい迷ったのでしたためます。(まあ 記事書くのは 1 時間以上つことるわけですが。)
タイムスタンプは特に以下とするべし
タイムスタンプ君はなんでこうも台パン仕様なんだろうか。
フィールド名 f: doc.data().containsKey("フィールド名")
? doc["フィールド名"].toDate()
: DateTime.now(),
※ Firestore の "タイムスタンプ" を flutter (dart) では DateTime を受けることとするとき。デフォルト値が "" とか 0 とかじゃアウトなので、こうせざるを得ない。 DateTime.now()
本編
データを取る方法は公式の通り。
A) 取りたい Document のIDが決まっているとき
final docRef = db.collection("コレクションの名前").doc("SF");
docRef.get().then(
(DocumentSnapshot doc) {
final data = doc.data() as Map<String, dynamic>;
// データを取った後の処理。諸々の変数に代入するとか
},
onError: (e) => print("Error getting document: $e"),
);
実際には、コレクションをごっそりとってきて使いまわすことも多いと思います。その場合はごっそりと以下のように取ります。
B) コレクション配下のDocumentをごっそりとるとき
await dbinstance.collection("new").get()
.then((datadesu) {
for (var doc in datadesu.docs) {
newsContent.add(News(
cid: doc.data().containsKey("cid") ? doc["cid"] : "",
content: doc.data().containsKey("content") ? doc["content"] : "",
title: doc.data().containsKey("title") ? doc["title"] : "",
createdDate: doc.data().containsKey("createdDate")
? doc["createdDate"].toDate()
: DateTime.now(),
));
}
}, onError: (e) {
print("全滅 $e");
});
Bに注目して 一つずつ見ていこう
1)"new" 配下のドキュメントを全部取ってきます。
dbinstance.collection("new").get()
2) んで定番なのですが、then() にてデータをわたして (datadesuのところ)
.then((datadesu) {
3)ドキュメントの分だけ 処理を繰り返します。 昔で言う for each みたいなもの。今は python 等のように (単品 in セット) の書き方のほうが多いかしら
for (var doc in datadesu.docs) {
4)データを、リストにぶち込んでいきます。
適当にProgramでそれぞれ変数に格納すればいいんだけど、おそらくこれが王道を逝くやりかた。List<"コレクション名"> listname = []; と宣言してからそこに add していく方法
/// ドキュメント一覧 (レコード) を格納するリストを作る
late List<News> newsContent = [];
/// 上の通り
await dbinstance.collection("new").get().then((datadesu) {
for (var doc in datadesu.docs) {
//// 取得したドキュメントを1つ1つ、リストに格納していく
newsContent.add(News(
cid: doc.data().containsKey("cid") ? doc["cid"] : "",
content: doc.data().containsKey("content") ? doc["content"] : "",
title: doc.data().containsKey("title") ? doc["title"] : "",
createdDate: doc.data().containsKey("createdDate")
? doc["createdDate"].toDate()
: DateTime.now(),
));
}
}, onError: (e) {
print("全滅 $e");
});
もちろんSQLのWhere区のようにフィールド名で条件を指定して、マッチする documentだけを引っ張ることもできます。ここでは割愛します。ただ、それをするためには、当該項目への Index 付与が必要(だったはず) です。
補足) ここでいう List<News> って?
.以下のように別のクラスにメンバー変数と、コンストラクターっぽいやつを定義します。別の dart ファイルに定義するのが定番。
class News {
final String cid;
final String content;
final String title;
final DateTime createdDate;
News({
required this.cid,
required this.content,
required this.title,
required this.createdDate
});
}
※news.dart を別にていぎしています。main の下でもいいけど。
補足) 以下の書き方が回りくどくない?
cid: doc.data().containsKey("cid") ? doc["cid"] : "",
cid : doc["cid"]
のほうがシンプル!ですが、 firebase firestore にデータを漏れ抜けなく格納できてるならこれでもいい物の、私はズボラなので、当該のフィールドを作らなかったり、カラで作成したりするので、、これをFlutterできちんと受け止めてあげる必要があります。例えば以下。4フィールドを想定しているのに、2フィールドしかありません。こういう時に、 doc["title"] / doc["createDate"] といった存在しないフィールドにアクセスしようとすると怒られて例外が出るので、doc.data().containsKey("フィールド名")でチェックしてお伺いを先にたてます。。あーめんど🫠。
この辺がRDBと違うところですね。RDBのテーブルなら、何もデータをぶち込まなければ、(Nullableデータなら)Nullで勝手に配備してくれます。FBだとそうもいきません。
おまけ - 覚えておきたい基本用語 (でたらめ)
RDB と若干用語が違うので、勝手な私の解釈だと以下のようにマッピングしている。まあRDBじゃないんだとは思いますが。。。
通常のRDBと違って、ドキュメントの1つ1つには "ID"というものがついており、レコードそのものの名称をつけることができる。例えば以下なら SF2 というIDを持つ。
IDを持つがゆえに、レコードをピンポイントでID指定して取れる。
db.collection("cities").doc("SF")
なお、何もしなければランダムな値が入る
あんまりまとまってなくてすみませんが、ここまで。
FBへのデータ上げ下げはトラブりやすいところなので、忘備録がどうしても必要でお目汚し失礼しました🙇