[Flutter]Cloud Firestoreからデータ一覧を取得してGridViewに表示する

非同期でFirestoreから取得したデータを一覧で表示するための実装について記事を書いていきたいと思います。データを一覧で表示する方法については、FlutterのGridViewウィジェットを利用する実装で紹介したいと思います。

# Firestoreのデータ構造
今回の記事で紹介するFirestoreのデータ構造は以下のとおりです。
Collectionの中にドキュメントが一覧であり、各ドキュメントはtitleを持っています。
![Firestoreデータ構造Sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545703/8d97f9ef-610e-9355-81c9-ed269cadd966.png)

# 全体の実装
最初に全体の実装ですが、以下のようになります。
この後の章で要点について解説していきます。
```dart
class SakeGridViewWidget extends StatelessWidget {
  final Color color;
  final String title;

  const SakeGridViewWidget({Key? key, required this.color, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    var assetsImage = "images/ic_sake.png";
    return MaterialApp(
      title: 'Grid List',
      home: Scaffold(
        body: StreamBuilder(
          stream: FirebaseFirestore.instance.collection('BrandList').snapshots(),
          builder: (BuildContext context,
            AsyncSnapshot<QuerySnapshot> snapshot) {
            if (!snapshot.hasData) {
              return const Center(
                child: CircularProgressIndicator(),
              );
            }

            return GridView.builder(
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  crossAxisSpacing: 10.0, // 縦
                  mainAxisSpacing: 10.0, // 横
                  childAspectRatio: 0.7),
              itemCount: snapshot.data!.docs.length,
              padding: const EdgeInsets.all(5.0),
              itemBuilder: (BuildContext context, int index) {
                return Container(
                  child: GestureDetector(
                    child: Column(
                      children: <Widget>[
                        Image.asset(assetsImage, fit: BoxFit.cover,),
                        Container(
                          margin: const EdgeInsets.all(10.0),
                          child: Text(snapshot.data!.docs[index]['title']),
                        ),
                      ],
                    )),
                  padding: const EdgeInsets.all(10.0),
                  decoration: BoxDecoration(
                    color: color,
                    boxShadow: const [
                      BoxShadow(
                        color: Colors.grey,
                        offset: Offset(5.0, 5.0),
                        blurRadius: 10.0,
                      )
                    ],
                  ),
                );
              },
            );
          }),
      ),
    );
  }
}
```

# Documentの一覧を非同期で取得する
Firestoreからデータの一覧を取得する場合、Collectionに対してスナップショットを取得する形となります。
スナップショットは非同期で取得する事になり、返り値は`Stream<QuerySnapshot<T>>`型となっているため、StreamBuilderウィジェットを利用して取得します。
```dart
        body: StreamBuilder(
          stream: FirebaseFirestore.instance.collection('BrandList').snapshots(),
```

# データ取得中のインジケータの表示
非同期でのデータ取得中であることがわかるように、取得中の間だけインジケータを表示する実装は以下の部分になります。データ取得が完了すると`snapshot`に値が入ることでif文を抜けられます。
```dart
          builder: (BuildContext context,
            AsyncSnapshot<QuerySnapshot> snapshot) {
            if (!snapshot.hasData) {
              return const Center(
                child: CircularProgressIndicator(),
              );
            }
```

# タイル状にデータを一覧表示
縦に3列のタイル状になるようにデータをGridViewに表示する実装は以下の部分になります。
```dart
            return GridView.builder(
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  crossAxisSpacing: 10.0, // 縦
                  mainAxisSpacing: 10.0, // 横
                  childAspectRatio: 0.7),
              itemCount: snapshot.data!.docs.length,
              padding: const EdgeInsets.all(5.0),
```
各パラメータの意味は以下となります。
`crossAxisCount`：縦に何列並べるかの設定
`crossAxisSpacing`：縦の並びの余白
`mainAxisSpacing`：横の並びの余白
`childAspectRatio`：１つのタイルの縦横比率
`itemCount`：表示するデータの個数
`padding`：GridViewの周りの余白

# Documentデータを各タイルに表示する
GridViewの各タイルにデータを表示する実装は以下の部分になります。
```dart
              itemBuilder: (BuildContext context, int index) {
                return Container(
                  child: GestureDetector(
                    child: Column(
                      children: <Widget>[
                        Image.asset(assetsImage, fit: BoxFit.cover,),
                        Container(
                          margin: const EdgeInsets.all(10.0),
                          child: Text(snapshot.data!.docs[index]['title']),
                        ),
                      ],
                    )),
```
`assetsImage`はサンプルのイメージ画像を表示しています。
`Container`部分が、コレクションのスナップショットから各Documentの`title`フィールドのデータの取得する処理となっています。`itemBuilder`の引数でもらっている`index`が１つ１つのDocumentデータにアクセスするインデックスとなっています。
2次元配列のようなアクセス方法が可能で、
`snapshot.data!.docs[0]['title']`なら`abcDoc`の`title`、
`snapshot.data!.docs[1]['title']`なら`bcdDoc`の`title`
・・・とアクセスできます。

# さいごに
私がFlutterを学ぶにあたり、参考にしたサイト集を別の記事でまとめていますので、こちらも是非読んでみてもらえたらと思います。

https://qiita.com/highcom/items/0e2a97e25497651377ca