FlutterアプリテンプレートへのCloud Firestoreの導入方法(hukusuke1007/flutter_app_template)

# はじめに

Flutter アプリテンプレート **hukusuke1007 / [flutter_app_template](https://github.com/hukusuke1007/flutter_app_template)** に Firebase の Cloud Firestore（NoSQL クラウド データベース）を導入する方法を確認してまとめます。

私は Cloud Firestore を利用するのが初めてで、NoSQL も理解していない状態であるため、これを機に入門したいと思います。

## hukusuke1007 / [flutter_app_template](https://github.com/hukusuke1007/flutter_app_template) について

https://github.com/hukusuke1007/flutter_app_template

flutter_app_template は初めて使ってみているのですが、Flutter + Firebase アプリのスターターキットになっており、サンプル機能がとても充実しています。序盤はコードリーディングしつつ Flutter の開発手法を学び、個人開発に移っていこうと思っています。

## ゴール

[README](https://github.com/hukusuke1007/flutter_app_template/tree/main/feature_first#%E6%B3%A8%E6%84%8F) にある以下のインデックス設定をして、タイムライン機能を確認することです。

> **注意**
> 
> 
> タイムライン機能を確認するためには、Firestoreのpostsコレクションのindexを設定してください。
> 
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/aa3589e5-9a14-0888-fd45-41f2c7d9c36e.png)

> 

ゴールまでの一連の手順を記事にまとめます。

## 前提

**hukusuke1007 / [flutter_app_template](https://github.com/hukusuke1007/flutter_app_template)** の導入（`git clone` から `flutter run` まで）が完了していることが前提です。

https://qiita.com/ryamate/items/f50aa769dbc34a39fa64

# 1. Cloud Firestore のセットアップ

Firebase コンソールで Cloud Firestore のセットアップをします。

## Cloud Firestore とは

> Google Cloud インフラストラクチャ上に構築された柔軟でスケーラブルな NoSQL クラウド データベースを使用して、クライアントサイドおよびサーバーサイド開発用のデータを保存および同期します。
> 

[Firestore  |  Firebase](https://firebase.google.com/docs/firestore?hl=ja)

## 1-1. Firebase プロジェクトを開く

Firebase コンソールにアクセスし、プロジェクトを開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/8f75f788-c02b-7aef-c478-768b367c3f89.png)

## 1-2. Cloud Firestore の有効化

「Cloud Firestore」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/8016c133-b177-2060-cf7b-0ed5db5fa18a.png)

「データベースの作成」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/93fd6809-6aec-38f1-3455-bc424c9c32f2.png)

「①名前とロケーションを設定する」では、

データベース ID は `(default)` 、

ロケーションは `asia-northeast1 (Tokyo)` に設定しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/17af77d9-baa1-635d-5da3-31e77b9430c6.png)

「②セキュリティルール」では、

開発環境用のプロジェクトには「テストモードで開始する」

本番環境用のプロジェクトには「本番環境モードで開始する」

を選択し、「作成」ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/7cf4ba78-d774-fba0-38f1-3dd8717237a6.png)

プロビジョニングが終わったら、以下のページが表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/3a7946dc-5738-c9ec-43ea-a559bb6d9d85.png)

# 2. インデックスの設定

一旦ゴールの再確認です。

> **注意**
> 
> 
> タイムライン機能を確認するためには、Firestoreのpostsコレクションのindexを設定してください。
> 
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/aa3589e5-9a14-0888-fd45-41f2c7d9c36e.png)
> 

現状は私にとって謎解きゲームみたいな状態なので、不明点を紐解きながら進みます。

## インデックスとは

> インデックスは、データベースのパフォーマンスにおける重要な要素です。書籍内のトピックをページ番号に対応付ける書籍の索引（インデックス）と同様に、データベースのインデックスはデータベース内のアイテムをデータベース内の場所にマッピングします。データベースにクエリが送信される際、データベースはインデックスを使用して、リクエストされたアイテムの場所をすばやく検索します。
> 

[Cloud Firestore のインデックスの種類  |  Firebase](https://firebase.google.com/docs/firestore/query-data/index-overview?hl=ja)

該当のデータを探す際に、インデックスを参照することで効率的にデータの場所を探せる、ということですね。

```mermaid
sequenceDiagram
  participant User as ユーザー
  participant Application as アプリ
  participant Firestore

  User ->> Application: ポスト一覧画面リクエスト
  Application ->> Firestore: クエリをFirestoreに送信
  Firestore ->> Firestore: インデックスを参照し、データの場所を特定
  Firestore ->> Firestore: データを取得
  Firestore -->> Application: クエリの結果を返す
  Application -->> User: ポスト一覧表示
```

## Cloud Firestore のデータモデル

「コレクション」という用語もわからないため、Cloud Firestore のデータモデルを確認します。

[Cloud Firestore データモデル  |  Firebase](https://firebase.google.com/docs/firestore/data-model?hl=ja)

Cloud Firestore のデータモデルは、「コレクション」の中に「ドキュメント」があり、ドキュメントにデータ（フィールド、サブコレクション）があります。

```
📁 コレクション
  └── 📄 ドキュメント
        ├── フィールド
        └── フィールド
```

```:例
📁 コレクション (例: posts)
  ├── 📄 ドキュメント (例: 4ao4L1TL4uk6Fqfwns3f)
  |     ├── postId: "4ao4L1TL4uk6Fqfwns3f"
  |     ├── userId: "fat4RsZAG1MBYDMymzuMPpIRdWg1"
  |     ├── text: "dekita!"
  |     ├── likeCount: 0
  |     ├── createdAt: 2024年6月23日 15:44:02 UTC+9
  |     └── updatedAt: 2024年6月23日 16:21:16 UTC+9
  └── 📄 ドキュメント (例: ltDcB3DFwQJJ4R1AtB6E)
        ├── postId: "ltDcB3DFwQJJ4R1AtB6E"
        ├── userId: "fat4RsZAG1MBYDMymzuMPpIRdWg1"
        ├── text: "dekiru?"
        ├── likeCount: 0
        ├── createdAt: 2024年6月23日 15:44:02 UTC+9
        └── updatedAt: 2024年6月23日 16:21:16 UTC+9
        
```

## 2-1. インデックスの作成


[Cloud Firestore でのインデックス管理 |  Firebase](https://firebase.google.com/docs/firestore/query-data/indexing?hl=ja#create_index)

「インデックス」タブを選択し、「インデックスを作成」をクリックします。「複合」と「単一フィールド」がありますが、[README](https://github.com/hukusuke1007/flutter_app_template/tree/main/feature_first#%E6%B3%A8%E6%84%8F) のスクリーンショットには「複合」とありますので、「複合」にしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/a9e69827-9579-47f1-1a89-97c2d7ad4d6b.png)

## 単一フィールド インデックスと複合インデックスとは

「単一フィールド インデックス」と「複合インデックス」について用語の確認をします。

[Cloud Firestore のインデックスの種類  |  Firebase](https://firebase.google.com/docs/firestore/query-data/index-overview?hl=ja)

Cloud Firestore では、単一フィールド インデックスと複合インデックスという 2 つのタイプがあります。

### 単一フィールド インデックス

- 特定の 1 つのフィールドを含む、コレクション内のすべてのドキュメントの並べ替え済みマッピングを保持する
- 単一フィールド インデックスの各エントリには、ドキュメント内の特定のフィールドの値と、そのドキュメントのデータベース内での位置が記録される

### 複合インデックス

- インデックスを作成するフィールドの順序付きリストに基づいて並べ替えられた、コレクション内のすべてのドキュメントのマッピングが格納される

## 2-2. インデックスの構成

コレクション ID に `posts` を入力します。

インデックスを作成するフィールドには以下を指定します。

- フィールド：`userId`、モード：`Ascending` 昇順
- フィールド：`createdAt`、モード：`Descending` 降順
- フィールド：`__name__`、モード：`Descending` 降順

クエリのスコープは `コレクション グループ` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/75ebdd18-05f0-8eed-983c-937e169bf8ab.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/b64a9ed2-bfa0-b67f-9cce-1c231f1c2f7a.png)

しばらく待つと、ステータスが「ビルド中…」から「有効」に変わりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/a3c10a7c-28e9-4011-664f-085e9f60a856.png)

複合インデックスの設定は完了しました。

動作確認します。

```bash
flutter run --debug --dart-define=FLAVOR=dev
```

タイムライン機能を確認します。

![スクリーンショット 2024-06-26 23.17.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/a30e2e38-657e-cc2c-7406-5de4b2977748.png)

タイムラインのサンプルをタップしたところ、タイムラインページではエラーが発生しました…

```bash
I/flutter (18127): 😡 SHOUT 2024-06-23 15:35:14.953840 [package:yomikey/features/timeline/pages/timeline_page.dart 210:18 in TimelinePage.build.<fn>] [cloud_firestore/failed-precondition] The query requires a COLLECTION_GROUP_DESC index for collection posts and field createdAt. You can create it here: https://console.firebase.google.com/v1/r/project/picture-book-log-develop/firestore/indexes?create_exemption=Cl1wcm9qZWN0cy9waWN0dXJlLWJvb2stbG9nLWRldmVsb3AvZGF0YWJhc2VzLyhkZWZhdWx0KS9jb2xsZWN0aW9uR3JvdXBzL3Bvc3RzL2ZpZWxkcy9jcmVhdGVkQXQQAhoNCgljcmVhdGVkQXQQAg
```

「クエリにはコレクション posts とフィールド createdAt のための COLLECTION_GROUP_DESC インデックスが必要です。こちらで作成できます: https://~~」とあり、失敗しているようです。

Firebase 公式ドキュメントの「 [**不足しているインデックスをエラー メッセージから作成する**](https://firebase.google.com/docs/firestore/query-data/indexing?hl=ja#create_a_missing_index_through_an_error_message) 」のとおり、こちらのURLをブラウザで開くと、インデックスの作成のページであるため、流れに従って保存を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/1d6a2060-0874-731a-deb3-16a1a78196c2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/9839337e-9bc0-7972-052d-2fcc1bafd67c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/f8d50d05-a6fe-9357-9317-e3e6e865a37e.png)

「 [**単一フィールド インデックス除外**](https://firebase.google.com/docs/firestore/query-data/index-overview?hl=ja&_gl=1*ttkjzl*_up*MQ..*_ga*MjAwNjQ1MTI1My4xNzE5MTg4MzE2*_ga_CW55HF8NVT*MTcxOTE4ODMxNy4xLjAuMTcxOTE4ODMxNy4wLjAuMA..#single-field_index_exemptions) 」が必要だったということのようです。

再度、タイムライン機能を確認します。

![スクリーンショット 2024-06-26 23.22.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/7816656b-d1ab-ae6b-193a-1b141ba96887.png)

今度はうまくいき、タイムラインに作成した内容が表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/527226/ed06a143-1d3d-7904-23ab-f49995b82cfc.png)

データが保存されています。

ひとまず今回のゴールには行きつきました！

ですが、頭の中にはてなマークが増えてきました… 公式ドキュメントの通読が必要であるととても感じます。

Firebase の公式ドキュメントは読みやすく充実している印象なので、一度目を通して、あとは使いながら把握していきたいと思います。

# おわりに

Flutter アプリテンプレート **hukusuke1007 / [flutter_app_template](https://github.com/hukusuke1007/flutter_app_template)** に Firebase の Cloud Firestore（NoSQL クラウド データベース）を導入する方法を確認してまとめました。

Cloud Firestore を利用するのは初めてでしたが、ほんの少し理解が進みました。

## やり残し

Flutter アプリテンプレートに関連して、以下を今後やっていこうと思います。

- Cloud Firestore 利用部分のコードリーディング
- Cloud Firestore の [公式ドキュメント](https://firebase.google.com/docs/firestore?hl=ja) 通読
- Mason を使った Feature ディレクトリの作成など、README に書いてある開発方法の確認
- プラグインの確認。プラグインやコードがあるかもしれませんが、不要と分かった段階で削除しながら、開発を進めていこうと思います
- [参考文献](https://github.com/hukusuke1007/flutter_app_template/tree/main?tab=readme-ov-file#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE) の通読

ありがとうございました。