はじめに
以前に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
メモ内容
試しに作るものは以前と同じ。
事前準備として、Firestore
にTestCollectionというコレクションを作成しておき、その中にtitle
というフィールドを持つドキュメントを1つだけ作成しておく。
リアルタイム更新のやり方を書く前に、まずは、リアルタイムではなくボタンをクリックして、データを追加した後に画面を更新するやり方について。setState()
を利用して状態を更新する様なプログラム。
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
を使ったコードを書いてみると以下の様なコードになる。
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
にコンソールなどからデータを直接入力してみると、すぐに反映されるはず。
(とりあえず動く様に修正したが、ベストプラクティスとは程遠い気がするので、また機会があれば綺麗な書き方も考えてみようと思う。)