🐷

Flutter+Firestoreでリアルタイム取得する

2021/06/14に公開

FlutterでFirestoreを使ってリアルタイムに値を取得する方法についてのメモ。

環境

  • macOS Big Sur 11.3.1
  • Flutter 2.2.0
  • cloud_firestore: ^2.2.0
  • firebase_core: ^1.2.0

成果物

  • 動作イメージ

Image from Gyazo

  • GitHub
    ソースの全体はこちら。

https://github.com/captain-blue210/flutter_todo_app/blob/main/lib/task_list.dart

ポイント

  • Cloud Firestoreから値を取得する

公式のサンプルをみると、基本的なメソッドはほぼ同じみたい。
他の言語でFirebaseを使ったことがあれば同じ要領で取得できる。

final Stream<QuerySnapshot> _usersStream = FirebaseFirestore.instance.collection('users').snapshots();

サンプルを参考に、コレクションをデータ作成日の降順でソートするのを追加して取得してみる。

stream: FirebaseFirestore.instance
          .collection('tasks')
          .orderBy('createdAt', descending: true)
          .snapshots(),
  • StreamBuilderを使う

StreamBuilderを使うとStreamをいい感じに管理してくれて、使われなくなったら自動で廃棄してくれるものらしい。
-> FlutterFire公式
-> Flutter StreamBuilderの説明

Firestoreから取得してきた値がstreamにセットされて、それがbuilderの第2引数で渡されて使えるようになるイメージ。

※以下、抜粋

Widget buildTaskList() {
   return StreamBuilder<QuerySnapshot>(
      stream: FirebaseFirestore.instance
            .collection('tasks')
            .orderBy('createdAt', descending: true)
            .snapshots(),
      builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
         // snapshotからデータを取り出して使う
      },
   );
}
  • 取得したデータから必要な値を取り出す

ListViewで取ってきたドキュメントの一覧を表示するために以下で取得できる。

   return ListView(
      children: snapshot.data!.docs.map((DocumentSnapshot document) {
         // documentからデータを取得する処理を書く
      }).toList(),
   )

このdata!!は「non-nullableな型にキャスト」することを明示するもの?らしい。
DartのNull Safetyがどんなものなのかはよくわからないのであとで調べる。

次にドキュメント内に登録されているデータを取得するために、以下のようにしてMapとして取り出す。

final data = document.data()! as Map<String, dynamic>;

これでドキュメント内のデータを使う準備ができたので、ほしいデータ名をキーとして指定して取り出して使う

// statusの値に応じてアイコンの表示を変える
icon: data['status'] == TaskLogic.statusToString(Status.doing)
      ? const Icon(Icons.check_box_outline_blank)
      : const Icon(Icons.check_box),

idも同じように取り出せる。

onPressed: () {
   TaskLogic.toggle(data['id'].toString());
},

今回の記事で扱った部分のソースコード全体

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_todo_app/Status.dart';
import 'package:flutter_todo_app/task_detail.dart';
import 'package:flutter_todo_app/task_logic.dart';

class TaskList extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Flexible(
      child: buildTaskList(),
    );
  }

  Widget buildTaskList() {
    return StreamBuilder<QuerySnapshot>(
      stream: FirebaseFirestore.instance
          .collection('tasks')
          .orderBy('createdAt', descending: true)
          .snapshots(),
      builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (!snapshot.hasData) {
          return const Center(
            child: const CircularProgressIndicator(),
          );
        }
        if (snapshot.hasError) {
          return const Text('Something went wrong');
        }
        return ListView(
          children: snapshot.data!.docs.map((DocumentSnapshot document) {
            final data = document.data()! as Map<String, dynamic>;
            return Card(
              child: ListTile(
                leading: IconButton(
                  icon: data['status'] == TaskLogic.statusToString(Status.doing)
                      ? const Icon(Icons.check_box_outline_blank)
                      : const Icon(Icons.check_box),
                  onPressed: () {
                    TaskLogic.toggle(data['id'].toString());
                  },
                ),
                title: Text('${data['taskName']}'),
                onTap: () {
                  Navigator.push(
                      context,
                      MaterialPageRoute<void>(
                        builder: (context) => TaskDetail(data['id'].toString()),
                      ));
                },
              ),
            );
          }).toList(),
        );
      },
    );
  }
}

最後に

Null Safetyというのがあんまりピンときてないのでちゃんと勉強しておきたい。

参考

Discussion