🔥

FlutterでFirestoreのサブコレクションからコレクショングループを使って横断的にドキュメントを取得する

2022/04/27に公開

自己紹介

はじめましての方もそうでない方も、レシピサイトやSNSなどメニューを横断的に取り込める

おうちメニュー

というプロダクトをやっているkentarohです。

iOS Android
App Store Play Store

本題

Firestore使ってますか?マネージドなサービスはいいですよね。Firestoreもレスポンスいいですし最高です。今日はコレクショングループでデータが取れない!ってちょっとはまったので共有させてもらいます。まぁ結局公式に書いてあって、検索して探さず公式丁寧に読もうよって話でしたが、そうやってここに来る人もいるのではと思って書いています。

前提知識 サブコレクション(subcollection)とは

Firestoreのデータ構造は、コレクション、ドキュメント、コレクション、ドキュメントという構造になています。あるドキュメントの下のコレクションのことをサブコレクションと呼びます。

コレクション ドキュメント サブコレクション ドキュメント
ルームズ ルームA メッセージズ メッセージA(ユーザーID、テキスト、作成日時)
メッセージズ メッセージB(ユーザーID、テキスト、作成日時)
ルームB メッセージズ メッセージC(ユーザーID、テキスト、作成日時)
メッセージズ メッセージD(ユーザーID、テキスト、作成日時)

ルームAのメッセージとルームBのメッセージを横断的に取得したいとき、どうしよう?って思うわけです。ルームAのサブコレクションを全部取得してー、ルームBのサブコレクションを取得してーと何回もクエリすることになります。いい方法ないでしょうか?

コレクショングループ(collectiongroup)があるんです

コレクショングループはドキュメントの下のサブコレクションをグループとして横断的に管理できるようになる仕組みです。ルームAとかBとか関係なく横断的に管理できます。

サブコレクション ドキュメント
メッセージズ メッセージA(ユーザーID、テキスト、作成日時)
メッセージズ メッセージB(ユーザーID、テキスト、作成日時)
メッセージズ メッセージC(ユーザーID、テキスト、作成日時)
メッセージズ メッセージD(ユーザーID、テキスト、作成日時)

このグループのことをコレクショングループと呼びます。
こうなれば、メッセージーズでクエリできてシンプルですね!

コレクショングループを使ってサブコレクションでクエリする

そんな便利なコレクショングループですが、使う上で制約があります。

1.インデックスを設定しないといけない
2.セキュリティルールの設定がちょっと特殊

セキュリティルールの設定

Firebaseの公式にも書いてあります。引用します。

セキュリティ ルールでは、コレクション グループのルールを記述して、コレクション グループのクエリを明示的に許可する必要があります。
rules_version = '2'; がルールセットの最初の行であることを確認します。コレクション グループ クエリには、セキュリティ ルール バージョン 2 の新しい再帰ワールドカード >{name=} の動作が必要です。
match /{path=
}/[COLLECTION_ID]/{doc} を使用して、コレクション グループのルールを記述します。

つまり例の場合のセキュリティルールは

service cloud.firestore {
  match /databases/{database}/documents {
    match /{path=**}/messages/{message} {
      // 下記はリードを許可する条件としてauthがあればということですがご自身の条件にあわせてカスタマイズしてください。
      allow read: if request.auth != null;
    }
  }
}

となります。

インデックスの設定

インデックスの設定がある意味コレクショングループの設定だと言えます。Firebaseにアクセスして設定しましょう。

こちらの【単一フィールド】を押しましょう。

右下の青い【除外を追加】を押しましょう。

こちらにサブコレクション名、検索で使わせるフィールド(ドキュメントの項目)を設定します。

私はここにハマりました。uidをフィールドで項目として持って設定して回避するといいでしょう。

Flutterからのクエリ

var user = firebase.auth().currentUser;
db.collectionGroup("messages").where("userId", "==", user.uid).get()

これで、あるユーザーがいろいろな部屋で発言したメッセージを横断的に取得できました!

参考

Firebase
https://firebase.google.com/docs/firestore/security/rules-query?hl=ja

感想

公式のドキュメントを読む癖をつけたい!


次回予告

だいたい毎回次回予告があてにならないのですが、次は動画リワード広告あたりを共有したいです。

Discussion