Contents
7. Read messages
Synchronize messages
ゲストがデータベースにメッセージを書き込むことができるのは素晴らしいことですが、アプリではまだメッセージを見ることができません。
メッセージを表示するには、データが変更されたときにトリガーされるリスナーを追加してから、新しいメッセージを表示するUI要素を作成する必要があります。 アプリから新しく追加されたメッセージをリッスンするコードをアプリケーションの状態に追加します。
lib / main.dartに、次のインポートを追加します。 これにより、StreamSubscriptionクラスにアクセスできるようになります。このクラスを使用して、ユーザーがログアウトしたときにクエリサブスクリプションをキャンセルできるようにします。
//lib.main.dart
import 'dart:async'; // new
次に、GuestBookウィジェットのすぐ上に次の値クラス(モデルクラス)があります。 このモデルクラスは、CloudFirestoreに保存しているメッセージデータの構造化ビューを公開します。
ウィジェットクラス内ではモデルクラスのインスタンスとしてメッセージデータを扱うのが定石。
モデルクラスを用意した方が、GuestBookMessageクラスに新しいフィールドを追加する必要が出た時などに、コードの修正が容易になる。
//lib/main.dart
class GuestBookMessage {
GuestBookMessage({@required this.name, @required this.message});
final String name;
final String message;
}
状態とゲッターを定義するApplicationStateのセクションに、次の新しい行を追加します。
//lib/main.dart
ApplicationLoginState _loginState;
ApplicationLoginState get loginState => _loginState;
String _email;
String get email => _email;
// Add from here
StreamSubscription<QuerySnapshot> _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// to here.
最後に、ApplicationStateの初期化セクションで、以下を追加して、ユーザーがログインしたときにドキュメントコレクションのクエリをサブスクライブし、ユーザーがログアウトしたときにサブスクライブを解除します。
//lib/main.dart
Future<void> init() async {
await Firebase.initializeApp();
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
// Add from here
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
snapshot.docs.forEach((document) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'],
message: document.data()['text'],
),
);
});
notifyListeners();
});
// to here.
} else {
_loginState = ApplicationLoginState.loggedOut;
// Add from here
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
notifyListeners();
});
}
このセクションは重要です。この箇所でguesbookコレクションに対するクエリを定義して、サブスクライビングとアンサブスクライビング(listenの開始と終了)を管理するからです。工事中🏗
You listen to the stream, where you reconstruct a local cache of the messages in the guestbook
collection,
そしてサブスクリプションへの参照を変数にセットすることで、後のアンサブスクライブ(listenの終了処理)を可能にします。
ここでは多くのことが行われているので、デバッガーで時間をかけて、より明確なメンタルモデルを取得するときに何が起こるかを調べる価値があります。
詳細については、 CloudFirestoreのドキュメントを参照してください。
ヒント:更新を高速化するには、リスト全体ではなく、変更されたドキュメントのみを更新できます。詳細については、 CloudFirestoreのドキュメントをご覧ください。
GuestBookウィジェット内で、状態の変化をユーザーインターフェースと結びつける必要があります。GuesBookウィジェットにメッセージのリストを追加するために、GuestBookウィジェットを下記のように修正します。
新しいメッセージがguestbookコレクションに追加される度に、ApplicationState._guestBookMessagesにそれが反映される(guestbookコレクションをlistenしているため)ので、その変化をGuestBookウィジェットに反映させる。
//lib/main.dart
class GuestBook extends StatefulWidget {
// Modify the following line
GuestBook({required this.addMessage, required this.messages});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
_GuestBookState createState() => _GuestBookState();
}
次に、_GuestBookStateのbuildメソッドを下記のように修正して、GuestBookでメッセージのリストが表示されるようにします。
lib/main.dart
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
// Modify from here
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// to here.
Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
),
// Modify from here
SizedBox(height: 8),
for (var message in widget.messages)
Paragraph('${message.name}: ${message.message}'),
SizedBox(height: 8),
// to here.
],
);
}
}
ビルドメソッドの前のコンテンツをColumn
ウィジェットでラップしてから、 Column
の子の末尾にコレクションを追加して、メッセージのリスト内の各メッセージの新しいParagraph
を生成します。
最後に、新しいmessages
パラメータを使用してGuestBook
を正しく構築するために、 HomePage
の本文を更新する必要があります。
lib / main.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loginState == ApplicationLoginState.loggedIn) ...[
Header('Discussion'),
GuestBook(
addMessage: (String message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages, // new
),
],
],
),
),
Test synchronizing messages
Cloud Firestoreは、データベースにサブスクライブしているクライアントとデータを自動的かつ即座に同期します。
- データベースで以前に作成したメッセージは、アプリに表示されます。新しいメッセージを自由に書いてください。それらは即座に表示されるはずです。
- ワークスペースを複数のウィンドウまたはタブで開くと、メッセージはタブ間でリアルタイムに同期されます。
- (オプション) Firebaseコンソールの[データベース]セクションで、新しいメッセージを手動で直接削除、変更、または追加してみることができます。変更はUIに表示されます。
おめでとう!アプリでCloudFirestoreドキュメントを読んでいます!
8.Set up basic security rules
参考
https://firebase.google.com/learn/codelabs/firebase-get-to-know-flutter#6