FlutterでFirestoreからのデータ取得（WidgetのBuilder以外で）

## はじめに
Flutterを使ってネイティブアプリを作っているが、Widgetの使い方やFirestoreとのデータ連携の仕方など、コードの書き方が分からずそれなりに苦労している。
一旦分かってしまえばかなり高速でアプリ開発ができそうなので、使い方やコードの書き方のメモを残しておく。
以前、[**【FlutterからCloud Firestoreのデータ取得 ＆ データ書き込み】**](https://qiita.com/smiler5617/items/8dcf720f5477a5f4b7d7)の記事を書いたが、これは取得したデータをWidgetで使うために`QuerySnapshot`の型で返していた。
単純なバックグラウンドの処理として、検索結果の値（任意のフィールドの値）を取得したり、取得結果により返り値を変えたりとしたかったのだが、ここに結構ハマってしまったので、一応やり方のメモを残しておく。


## Flutterの実行環境

- Ubuntu 18.04LTS（GCP上）
- Flutter 1.22.6
- Dart 2.10.5
- Android Studio 4.1.2
- VScode 1.53.0
　　

## メモ内容
事前準備として、`Firestore`に以下の様な`TestCollection`を作成し、複数ドキュメントを作っておく。
![Qiita-No034_img01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654767/f2ce0088-1cf3-73a1-7628-95f4255f8fe9.jpeg)
※今回は、`title`フィールドで検索をかけるため、`title:"aaaa"`、`title:"bbbb"`の様に各ドキュメントに適当な値の`title`フィールドを作成している。


上記を前提に、以下の処理をしたい場合の実装方法でハマったため、その時のソースをメモとして残しておく。
　① Whereを用いて`TestCOllection`より、該当のdocumentを取得する。
　② 該当するドキュメント数を表示する。

今回も`main.dart`にソースを集約しているので、以下を参考に。

```dart:main.dart
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

void main() {
 runApp(App());
}

class App extends StatelessWidget {

 @override
 Widget build(BuildContext context) {

    return MaterialApp(
      debugShowCheckedModeBanner: true,  // <- Debug の 表示を OFF

      home: TestPage(),
    );
  }
}

class TestPage extends StatelessWidget {
  
  // Firestore ためのインスタンスを作成。
  final firestore_ope = Firestore_ope();

  // 検索ワード（入力文字）の受け取り用変数
  var input_word = TextEditingController();

  // タイトル名から検索した後の、該当するdocument数を受け取る変数
  int document_num;

  @override
   Widget build(BuildContext context) {

    return Scaffold(
        body: Center(child: Text("UI_test")), // <- Text の位置を指定

        // 検索ワードを入力するためのボタン
        floatingActionButton: Container(
          margin: EdgeInsets.only(bottom: 10.0), // ボタンの配置

          child: FloatingActionButton.extended(
           backgroundColor: Colors.green,
            icon: Icon(Icons.search),
            label: Text("TEST"),

            // ボタンクリック時の処理
            onPressed: () => showDialog(
                context: context,
                child: AlertDialog(
                  title: Text(
                    'タイトル名で検索',
                    // style: TextStyle(fontFamily: "Smash"),
                  ),
                  content: TextField(
                    decoration: InputDecoration(
                      hintText: '検索ワードを入力してください。',
                    ),
                    controller: input_word,
                    autofocus: true,
                      // keyboardType: TextInputType.number,
                  ),
                  actions: <Widget>[
                    FlatButton(
                      child: Text('キャンセル'),
                      onPressed: () {
                        Navigator.pop(context);
                    }),
                    FlatButton(
                      child: Text('検索'),

                      // 表示されたポップアップで『検索』をクリックした時の処理
                      onPressed: () {
                            Navigator.pop(context);

                            // 検索ワードを渡して、ドキュメント数を返してくれるメソッドを利用。
                            document_num = firestore_ope.search_document_with_title(input_word.text);
                            showDialog(
                                context: context,
                                child: AlertDialog(
                                    content: Text('該当ドキュメントの数は『' + document_num.toString() + '』です。'),
                                    actions: <Widget>[
                                      FlatButton(
                                        child: Text('OK'),
                                        onPressed: () {
                                          Navigator.pop(context);
                                       }),
                                  ]),
                            );  
                      }   
                    ),
                  ]),
                ),
           ),
          ),
      );
 }
}


//Firestoreを操作するためのクラス
class Firestore_ope {
  static const String _collection = 'TestCollection';

  // 検索ワードにヒットするタイトル名のドキュメント数を返すメソッド。
  search_document_with_title(String title) {

    // 操作するコレクションを設定
    const String target_colc = _collection;

    // 返り値として渡すためのドキュメント数を入れる変数
    int document_num;

    Future<QuerySnapshot> snapshot;

    // 検索結果は、一旦はFuture型で取得
    snapshot =  Firestore.instance
        .collection(target_colc)
        .where('title', isEqualTo: title)
        .getDocuments();

    // ドキュメント数を取得。
    snapshot.then((value) {
      print(value.documents.length);
      document_num = value.documents.length;
    });

    return document_num;
  }
}

```

上のコードを実行して、試しに検索すると以下の様になるはず。
![Qiita-No034_img02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654767/d2294116-294a-9040-c34e-3d3f9e47606f.jpeg)
※`TERMINAL`上では、ちゃんと数字が出力されている。

これは一般的な非同期処理と同じで、検索結果の返り値を待たずに次の`AlertDialog`が処理されてしまうため。
なので、以下の様に`async/await`を入れてみる。

```dart:main.dart
---
省略
---
// 表示されたポップアップで『検索』をクリックした時の処理
                      onPressed: () async {
                            Navigator.pop(context);

                            // 検索ワードを渡して、ドキュメント数を返してくれるメソッドを利用。
                            document_num = await firestore_ope.search_document_with_title(input_word.text);
                            showDialog(
                                context: context,
                                child: AlertDialog(
                                    content: Text('該当ドキュメントの数は『' + document_num.toString() + '』つです。'),
                                    actions: <Widget>[
                                      FlatButton(
                                        child: Text('OK'),
                                        onPressed: () {
                                          Navigator.pop(context);
                                       }),
                                  ]),
                            );  
                      }   
---
省略
---

```

実は上のソースでも返り値を待たずに処理されてしまう。
原因は呼び出しているメソッドの方の処理で、ドキュメント数を取得している部分が非同期処理されており、メソッドからの`return`自体は先行して完了してしまっているため。
なので、上記に加えて、以下の部分にも`async/await`を入れてあげる。

```dart:main.dart
---
省略
---
//Firestoreを操作するためのクラス
class Firestore_ope {
  static const String _collection = 'TestCollection';

  // 検索ワードにヒットするタイトル名のドキュメント数を返すメソッド。
  search_document_with_title(String title) async {

    // 操作するコレクションを設定
    const String target_colc = _collection;

    // 返り値として渡すためのドキュメント数を入れる変数
    int document_num;

    Future<QuerySnapshot> snapshot;

    // 検索結果は、一旦はFuture型で取得
    snapshot =  Firestore.instance
        .collection(target_colc)
        .where('title', isEqualTo: title)
        .getDocuments();

    // ドキュメント数を取得。
    await snapshot.then((value) {
      print(value.documents.length);
      document_num = value.documents.length;
    });

    return document_num;
  }
}

```

これで試してみると、ちゃんと取得した情報を表示してくれるはず！

一応、最終版のコードは以下に貼っておきます。

```dart:main.dart
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

void main() {
 runApp(App());
}

class App extends StatelessWidget {

 @override
 Widget build(BuildContext context) {

    return MaterialApp(
      debugShowCheckedModeBanner: true,  // <- Debug の 表示を OFF

      home: TestPage(),
    );
  }
}

class TestPage extends StatelessWidget {

  // Firestore ためのインスタンスを作成。
  final firestore_ope = Firestore_ope();

  // 検索ワード（入力文字）の受け取り用変数
  var input_word = TextEditingController();

  // タイトル名から検索した後の、該当するdocument数を受け取る変数
  int document_num;

  @override
   Widget build(BuildContext context) {

    return Scaffold(
        body: Center(child: Text("UI_test")), // <- Text の位置を指定

        // 検索ワードを入力するためのボタン
        floatingActionButton: Container(
          margin: EdgeInsets.only(bottom: 10.0), // ボタンの配置

          child: FloatingActionButton.extended(
           backgroundColor: Colors.green,
            icon: Icon(Icons.search),
            label: Text("TEST"),

            // ボタンクリック時の処理
            onPressed: () => showDialog(
                context: context,
                child: AlertDialog(
                  title: Text(
                    'タイトル名で検索',
                    // style: TextStyle(fontFamily: "Smash"),
                  ),
                  content: TextField(
                    decoration: InputDecoration(
                      hintText: '検索ワードを入力してください。',
                    ),
                    controller: input_word,
                    autofocus: true,
                      // keyboardType: TextInputType.number,
                  ),
                  actions: <Widget>[
                    FlatButton(
                      child: Text('キャンセル'),
                      onPressed: () {
                        Navigator.pop(context);
                    }),
                    FlatButton(
                      child: Text('検索'),

                      // 表示されたポップアップで『検索』をクリックした時の処理
                      onPressed: () async {
                            Navigator.pop(context);

                            // 検索ワードを渡して、ドキュメント数を返してくれるメソッドを利用。
                            document_num = await firestore_ope.search_document_with_title(input_word.text);
                            showDialog(
                                context: context,
                                child: AlertDialog(
                                    content: Text('該当ドキュメントの数は『' + document_num.toString() + '』つです。'),
                                    actions: <Widget>[
                                      FlatButton(
                                        child: Text('OK'),
                                        onPressed: () {
                                          Navigator.pop(context);
                                       }),
                                  ]),
                            );  
                      }    
                    ),
                  ]),
                ),
           ),
          ),
      );
 }
}


//Firestoreを操作するためのクラス
class Firestore_ope {
  static const String _collection = 'TestCollection';

  // 検索ワードにヒットするタイトル名のドキュメント数を返すメソッド。
  search_document_with_title(String title) async {

    // 操作するコレクションを設定
    const String target_colc = _collection;

    // 返り値として渡すためのドキュメント数を入れる変数
    int document_num;

    Future<QuerySnapshot> snapshot;

    // 検索結果は、一旦はFuture型で取得
    snapshot =  Firestore.instance
        .collection(target_colc)
        .where('title', isEqualTo: title)
        .getDocuments();

    // ドキュメント数を取得。
    await snapshot.then((value) {
      print(value.documents.length);
      document_num = value.documents.length;
    });

    return document_num;
  }
}
```