Flutterでfirestoreを触ってみる ※2024/10/30更新

# はじめに
Firebase Realtime Databaseを触っていたけど、Firestoreはどうなんだろう？
と思って触ってみる。
場合によっては置き換えも視野に入れているので触らざるを得ない！
おそらく時々更新すると思います。

とりあえず、[Cloud Firestore を使ってみる](https://firebase.google.com/docs/firestore/quickstart?hl=ja&_gl=1*1jbn1as*_up*MQ..*_ga*MTAwNjAyMTA4MC4xNzMwMjA4NTAx*_ga_CW55HF8NVT*MTczMDIwODUwMS4xLjAuMTczMDIwODUwMS4wLjAuMA..)で準備を終えておきましょう。



記事を読んで感想や指摘等あればコメントでお願いします。
リアクションがあるとちょっとうれしいです！

※今回のコードは https://github.com/project-dev/flutter_firebase_realtime_database_test に追加してあります。

# ログの出力メソッド

とりあえず画面とコンソールにログを出したかったので以下のメソッドを作って処理させています。
Loggerは自作のログ出力クラス。
```dart
  void _addLog(String text){
    setState(() {
      _logController.text += "$text\n";
    });
    Logger.info(text);
  }
```

# 書き込み

まずはデータを書き込まないと始まらない

## 新しいコレクションを作成してドキュメントを書き込む

collection()メソッドでコレクション名を指定し、addでドキュメントに格納するフィールドを指定します。
ドキュメントのIDは自動で割り当てられて登録されます。

```dart
      FirebaseFirestore.instance.collection('users2').add({
        'name':'aaaaa',
        'age':24
      });
```

実際にはこのように格納されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277984/bb5ec139-9ba6-151c-182b-cc7c46b7e698.png)

## 既存のコレクションへドキュメントIDを指定して追加する

CollectionReferenceクラスのdoc()メソッドの引数にドキュメント名を指定し、set()メソッドでフィールドを指定します。

```dart
      CollectionReference users = FirebaseFirestore.instance.collection ('users');
      await users.doc("user1").set(
          {
            "name":"名前",
            "age":"45"
          }
      );
```

実際にはこのように格納されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277984/4178ed54-af5a-906f-c05c-de7af76ad795.png)


## add()メソッドとset()メソッドのポイント
ドキュメントに値を設定する方法はadd()メソッドとset()メソッドの2つがあります。
add()メソッドではドキュメントのIDが自動で生成され、フィールドが設定されます。
set()メソッドはドキュメントIDを設定してフィールドを設定します。

# 更新

更新は、DocumentReferenceメソッドのset()メソッド、要するに書き込みで使用したset()メソッドで可能です。

```dart
  Future<void> _updateItem() async{
    CollectionReference users = FirebaseFirestore.instance.collection ('users');
    await users.doc("user1").set(
        {
          "name":"名前更新",
          "age":"100"
        }
    );
  }
```

# 読み込み


## 単一ドキュメントの読み込み
コレクションを名前を指定して CollectionReference オブジェクトを取得します。
その後、CollectionReferenceクラスのdoc()メソッドで取得したドキュメントIDを指定します。
DocumentReferenceオブジェクトが取得できるので、get()メソッドでフィールドを取得します。

```dart
  Future<void> _getData() async{
    // Userコレクションを取得
    var data = await FirebaseFirestore.instance.collection('users').doc('user1').get();
    var fields = data.data();
    _addLog("Name : ${fields?['name']} / Age : ${fields?['age']}");
  }
```

## まとめてドキュメントを読み込み
コレクションを名前を指定して CollectionReference オブジェクトを取得します。
CollectionReferenceクラスの get() メソッドで QuerySnapshotオブジェクト を取得します。
QuerySnapshotオブジェクト のdocsをループで回して、docsの要素である QueryDocumentSnapshot オブジェクトからdata()メソッドでフィールドを取得します。

```dart
  Future<void> _viewData() async{
    // Userコレクションを取得
    CollectionReference users = FirebaseFirestore.instance.collection ('users');

    // スナップショットからクエリを取得
    var querySnapshot = await users.get();

    // ドキュメントを表示
    for (var doc in querySnapshot.docs) {
      _addLog("ID : ${doc.id} / DATA : ${doc.data()}");
    }
  }
```

## データクラスにデータを格納する

データクラスというかModelというか、クラスにフィールドの内容を格納します。
 [参考にしたサイト](https://zenn.dev/ryo_t/articles/5dd6970f2e3f06)の内容と同じですが・・・

データクラス
```Dart:persoin_model.dart
class PersonModel{
  String name;
  int age;

  PersonModel({required this.name, required this.age});
}
```


```dart:データ取得
  Future<void> _getDataModel() async{
    // Userコレクションを取得
    var users = await FirebaseFirestore.instance.collection ('users').get();
    var persons = users.docs.map((doc) => PersonModel(
      name: doc['name'],
      age: int.parse(doc['age'].toString())
    )).toList();
    _addLog("data length${persons.length}");
    for (var model in persons) {
      _addLog("Name : ${model.name} / Age : ${model.age}");
    }
  }
```

# 削除
QueryDocumentSnapshotクラスの reference変数を使って削除を行います。
```dart
oc.reference.delete()
```


# オフラインでのデータの永続性の設定

以下のように設定すれば永続化されます。

```dart
FirebaseFirestore.instance.settings = const Settings(persistenceEnabled: true);
```

# 条件指定での抽出

whereで条件を指定して抽出ができる。

```dart
  Future<void> _getAge20Over() async{
    // Userコレクションを取得
    var collectionRef = FirebaseFirestore.instance.collection('users');
    var query = collectionRef.where("age", isGreaterThanOrEqualTo: 20);
    var querySnapshot = await query.get();

    _addLog("data length ${querySnapshot.docs.length}");
    for (var doc in querySnapshot.docs) {
      _addLog("ID : ${doc.id} / DATA : ${doc.data()}");
    }
  }
```

そのほかにも様々な抽出方法がある。
そして、制限もあるため、[Cloud Firestore で単純なクエリと複合クエリを実行する](https://firebase.google.com/docs/firestore/query-data/queries?hl=ja&_gl=1*1rjixc7*_up*MQ..*_ga*MTAwNjAyMTA4MC4xNzMwMjA4NTAx*_ga_CW55HF8NVT*MTczMDIwODUwMS4xLjAuMTczMDIwOTA0My4wLjAuMA..) を参考にすべし。

# おまけ

軽くトラブったのでおまけのメモ

## FirebaseAuth.instance.signInWithCredential(credential)でype 'List<Object?>' is not a subtype of type 'PigeonUserDetails?' in type castが発生する

[公式の対応](https://github.com/firebase/flutterfire/issues/13077)はダメだった
pubspec.ymlに記載されているFirebase関連のバージョンを変更し、flutter pub getしたら解決した。
(結局原因はよくわからず・・・)

# 参考
 - [Cloud Firestore を使ってみる](https://firebase.google.com/docs/firestore/quickstart?hl=ja&_gl=1*1jbn1as*_up*MQ..*_ga*MTAwNjAyMTA4MC4xNzMwMjA4NTAx*_ga_CW55HF8NVT*MTczMDIwODUwMS4xLjAuMTczMDIwODUwMS4wLjAuMA..)
- [オフラインでデータにアクセスする](https://firebase.google.com/docs/firestore/manage-data/enable-offline?hl=ja)
- [Cloud Firestore で単純なクエリと複合クエリを実行する](https://firebase.google.com/docs/firestore/query-data/queries?hl=ja&_gl=1*1rjixc7*_up*MQ..*_ga*MTAwNjAyMTA4MC4xNzMwMjA4NTAx*_ga_CW55HF8NVT*MTczMDIwODUwMS4xLjAuMTczMDIwOTA0My4wLjAuMA..)
- [【Flutter & FireStore】Modelを作成して、データの一覧を取得 する](https://zenn.dev/ryo_t/articles/5dd6970f2e3f06)