FlutterでFirestoreのデータをFutureBuilderを使って非同期で取得する

#目的
今まではFirestoreから取得したデータを取得できたときにsetState()で再描画していたのですが、FutureBuilderを使えばStatelessWidgetにできて、簡単にきれいに書けることを知ったのでまとめてみます。
setState()で再描画するだけの処理にするのではなく、エラー分岐やデータ取得状況もわかるFutureBuilderを使ったほうがUXも高めれて良さそうなので積極的に使っていきたいですね。

#環境
今回使っているFirestoreのバージョンは0.14.0です。
firebase_coreは0.5.0です。

```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^0.5.0
  cloud_firestore: ^0.14.0
```

#非同期処理で使う関数
まずはFirestoreからデータを取得するFutureオブジェクトを作成します。
今回は指定のドキュメントIDのデータを取得するという簡単な関数にします。
コレクションIDとドキュメントIDを自身のものに置き換えてください。

```dart
Future<Map<String, dynamic>> getDocumentData() async {
  DocumentSnapshot snapshot = await FirebaseFirestore.instance
      .collection('コレクションのID')
      .doc('ドキュメントのID')
      .get();
  return snapshot.data();
}
```

#FutureBuilderの使い方
Widgetのbuild()にFutureBuilderを追加します。
今回はScaffoldのbodyに直接置いていますが、実際はFirestoreのデータを表示したいところに追加してください。

FutureBuilderの`future`プロパティには作成したFutureの関数を指定します。
`builder`プロパティには(context, snapshot) { }を指定します。
一度、builderプロパティの関数が呼ばれます。
非同期処理が終わったら、再度builderが呼ばれて、エラー処理やデータが取得できなかったときに適切なWidgetを表示するようにしています。

```dart
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Flutter × Firestore'),
    ),
    body: FutureBuilder(
      future: getDocumentData(),
      builder: (context, snapshot) {
        // 取得が完了していないときに表示するWidget
        if (snapshot.connectionState != ConnectionState.done) {
          // インジケーターを回しておきます
          return const CircularProgressIndicator();
        }

        // エラー時に表示するWidget
        if (snapshot.hasError) {
          print(snapshot.error);
          return Text('エラー');
        }

        // データが取得できなかったときに表示するWidget
        if (!snapshot.hasData) {
          return Text('データがない');
        }

        // 取得したデータを表示するWidget
        return Text('${snapshot.data}');
      },
    ),
  );
}
```

#取得したsnapshotの使い方
snapshotはAsyncSnapshot型なので、
FutureBuilderで取得したデータは`snapshot.data`でアクセス出来ます。

今回取得できるデータはFirestoreのデータなのでsnapshot.data['key']のようにFirestoreで指定したKeyを書けば取得できます。

`builder`プロパティでsnapshotに(context, AsyncSnapshot<Map<String, dynamic>> snapshot) { }のように型を指定することも出来ます。

#クラス全体のコード

```dart
class TestWidget extends StatelessWidget {
  Future<Map<String, dynamic>> getDocumentData() async {
    DocumentSnapshot snapshot = await FirebaseFirestore.instance
        .collection('コレクションのID')
        .doc('ドキュメントのID')
        .get();
    return snapshot.data();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter × Firebase'),
      ),
      body: FutureBuilder(
        future: getDocumentData(),
        builder: (context, snapshot) {
          // 取得が完了していないときに表示するWidget
          if (snapshot.connectionState != ConnectionState.done) {
            return const CircularProgressIndicator();
          }

          // エラー時に表示するWidget
          if (snapshot.hasError) {
            print(snapshot.error);
            return Text('エラー');
          }

          // データが取得できなかったときに表示するWidget
          if (!snapshot.hasData) {
            return Text('データがない');
          }

          // 取得したデータを表示するWidget
          return Text('${snapshot.data}');
        },
      ),
    );
  }
}
```

#さいごに
Firestoreからデータを取得するたびにsetState()を呼ぶよりも、FutureBuilderを使ったほうがコードがきれいに書けて、取得状況もユーザーに知らせられるのでとても良いWidgetだと思います。
簡単に実装できるのでぜひ使ってみてください！
もし間違っている所があれば、ぜひ教えて下さい！