6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FlutterでFirestoreと連携したリアルタイム更新(Mac M1)

Last updated at Posted at 2021-11-14

はじめに

以前にFlutterからFirestoreを利用する方法まとめたが、今回Macでそれを試したら、Flutterやパッケージのバージョンが上がっていたため動かなかった。
大した変更ではないが、一応整理し直したのでメモとして残しておく。
以前の記事はこちら。【FlutterでFirestoreと連携したリアルタイム更新】

実行環境

【PC】
 MacBook Air (M1, 2020)

【各SWバージョン】
 ・macOS Big Sur 11.6.1
 ・Flutter 2.5.3 (dart 2.14.4)
 ・Xcode 13.1
 ・Cocoapods 1.11.2
・VScode(AplleSilicon) 1.62.2

【パッケージ】
 ・firebase_core: ^1.10.0
 ・cloud_firestore: ^3.1.0
  

メモ内容

試しに作るものは以前と同じ。
事前準備として、FirestoreTestCollectionというコレクションを作成しておき、その中にtitleというフィールドを持つドキュメントを1つだけ作成しておく。
Qiita-No033_img01.jpg

リアルタイム更新のやり方を書く前に、まずは、リアルタイムではなくボタンをクリックして、データを追加した後に画面を更新するやり方について。setState()を利用して状態を更新する様なプログラム。

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(App());
}

class App extends StatefulWidget {
  @override
  _AppPage createState() => _AppPage();
}

class _AppPage extends State<App> {
  // ボタンクリックの挙動で使う変数
  var insert_data;
  int doc_num = 0;
  late String add_number;
  late String add_title;

  // Firestoreからのデータを格納しておく変数
  List<DocumentSnapshot> fire_documents = [];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        // Stateの更新時に、Widgetが構築される
        body: FutureBuilder<QuerySnapshot>(
          future: FirebaseFirestore.instance.collection('TestCollection').get(),
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              // List<DocumentSnapshot>`をsnapshotから取り出す。
              if (snapshot.data != null) {
                fire_documents = snapshot.data!.docs;
              }

              return ListView.builder(
                shrinkWrap: true,
                itemCount: fire_documents.length, //配列の長さの分だけ作成する。
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(fire_documents[index]["title"]),
                  );
                },
              );
            } else if (snapshot.hasError) {
              return Center(child: Text('snapshot Error1'));
            } else {
              return Center(child: Text('snapshot Error2'));
            }
          },
        ),

        // 追加ボタン
        floatingActionButton: Container(
          margin: EdgeInsets.only(bottom: 10.0), // ボタンの配置
          //width: 40.0, // ボタンのサイズ。形にも依るが先に記載されている大きさが優先っぽい。
          //height: 40.0,

          child: FloatingActionButton.extended(
            backgroundColor: Colors.blue,
            icon: Icon(Icons.add),
            label: Text("追加"),

            // テーマ追加ボタンクリック時の処理 ⇒ ダイアログ立ち上げる
            onPressed: () => set_data(),
          ),
        ),
      ),
    );
  }

  // ボタンクリック時のアクション内容
  set_data() {
    if (fire_documents.length == null) {
      doc_num = 0;
    } else {
      doc_num = fire_documents.length;
    }

    add_number = (doc_num + 1).toString();
    add_title = 'title' '$add_number';

    insert_data = {
      'title': add_title,
    };
    FirebaseFirestore.instance.collection('TestCollection').add(insert_data);

    setState(() {});
  }
}

以前との変更点は以下7つ。
nullが入り得る変数にlate?!の宣言。どの条件分岐でもreturnが返る様に修正。
 ※Flutter2がサポートしているDart2.12からNullを許容しなくなった(null safety)ので、明示的に書く必要がある。
import 'package:firebase_core/firebase_core.dart';の追加
runApp()の前に以下を2つの行を追加。
  □WidgetsFlutterBinding.ensureInitialized();
  □await Firebase.initializeApp();
Firestoreのメソッド名をFirebaseFirestoreに変更。
・Firestoreからのドキュメント取得を .getDocuments()ではなく、.get()に変更。
・ドキュメント内のデータ取得も snapshot.documentsではなく、snapshot.docsに変更。

上記のコードは、右下のFloatingボタンをクリックすると、Firestoreにデータが追加されてsetState()することで再描画される仕組みになっている。 ※細かな作り込みはしてないので悪しからず。。。

しかし、上記のコードだと別の誰かがFirestoreに書き込みをして中身が変わっていても、Stateが更新されない限りは反映されない。

そこで、リアルタイムに反映するために使えるのがStreamBuilder
StreamBuilderを使ったコードを書いてみると以下の様なコードになる。

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

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(App());
}

class App extends StatefulWidget {
  @override
  _AppPage createState() => _AppPage();
}

class _AppPage extends State<App> {
  // ボタンクリックの挙動で使う変数
  var insert_data;
  int doc_num = 0;
  late String add_number;
  late String add_title;

  // Firestoreからのデータを格納しておく変数
  List<DocumentSnapshot> fire_documents = [];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        // リアルタイム更新  監視先は指定のコレクション全体
        body: StreamBuilder<QuerySnapshot>(
            stream: FirebaseFirestore.instance
                .collection('TestCollection')
                .snapshots(),
            builder:
                (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
              if (snapshot.hasError) {
                return Text('Something went wrong');
              }

              if (snapshot.connectionState == ConnectionState.waiting) {
                return Text("Loading");
              }

              // List<DocumentSnapshot>`をsnapshotから取り出す。
              if (snapshot.data != null) {
                fire_documents = snapshot.data!.docs;
              }

              // 中身のリスト表示部分
              return ListView.builder(
                // padding: const EdgeInsets.all(8),
                shrinkWrap: true,
                itemCount: fire_documents.length, //配列の長さの分だけ作成する。
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(fire_documents[index]["title"]),
                  );
                },
              );
            }),

        // 追加ボタン
        floatingActionButton: Container(
          margin: EdgeInsets.only(bottom: 10.0), // ボタンの配置
          //width: 40.0, // ボタンのサイズ。形にも依るが先に記載されている大きさが優先っぽい。
          //height: 40.0,

          child: FloatingActionButton.extended(
            backgroundColor: Colors.blue,
            icon: Icon(Icons.add),
            label: Text("追加"),

            // テーマ追加ボタンクリック時の処理 ⇒ ダイアログ立ち上げる
            onPressed: () => set_data(),
          ),
        ),
      ),
    );
  }

  // ボタンクリック時のアクション内容
  set_data() {
    if (fire_documents.length == null) {
      doc_num = 0;
    } else {
      doc_num = fire_documents.length;
    }

    add_number = (doc_num + 1).toString();
    add_title = 'title' '$add_number';

    insert_data = {
      'title': add_title,
    };
    FirebaseFirestore.instance.collection('TestCollection').add(insert_data);

    setState(() {});
  }
}

修正箇所は前半のコードとほとんど同じようなところ。

上記のコードは、画面上はあまり変化がない様に見えるが、試しにFirestoreにコンソールなどからデータを直接入力してみると、すぐに反映されるはず。

(とりあえず動く様に修正したが、ベストプラクティスとは程遠い気がするので、また機会があれば綺麗な書き方も考えてみようと思う。)

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?