この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
大阪オフィスの山田です。AmazonでのEssential phoneのセールを見逃して泣きそうでした。今回はFirebase Realtime DatabaseをFlutterから使用してみたいと思います。Firebase Realtime Databaseを使うこと自体初めてです。
今回やること
Firebase Realtime Databaseに対して、一覧取得、追加をFlutterで作ったアプリから行います。ライブラリ、firebase_databaseを使用します。Exampleも存在しますが、今回はもっと簡潔にした実装をしていきます。イメージとしてはこのようなアプリです。
入力したら、リストに追加されていくだけのシンプルなチャットです(果たしてチャットと呼んで良いか疑わしいレベル)。もちろん、他の端末から見ると、投稿がリストに表示されるようにします。
開発環境
flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel beta, v0.5.1, on Mac OS X 10.13.5 17F77, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK 28.0.1)
[✓] iOS toolchain - develop for iOS devices (Xcode 9.4)
[✓] Android Studio (version 3.1)
✗ Flutter plugin not installed; this adds Flutter specific functionality.
✗ Dart plugin not installed; this adds Dart specific functionality.
[!] VS Code (version 1.26.1)
[✓] Connected devices (1 available)
flutter --version
Flutter 0.5.1 • channel beta • https://github.com/flutter/flutter.git
Framework • revision c7ea3ca377 (3 months ago) • 2018-05-29 21:07:33 +0200
Engine • revision 1ed25ca7b7
Tools • Dart 2.0.0-dev.58.0.flutter-f981f09760
準備
各プラットフォームの設定
Android、iOS、各プラットフォームのプロジェクトをFirebase上に作成し、google-service.json
、GoogleService-Info.plist
をプロジェクトに追加します。こちらの記事の「準備」の章で各プラットフォームで設定をしていますのでご参照ください。
Firebase Realtime Databaseの準備
Databaseを用意します。Firebase Consoleにログインして、Databaseを選択します。
Realtime Databaseまでスクロールして、「データベースを作成」を選びます。
テストモードで作成し、誰でも読み書きできるようにします。
データベースが作成されました。
実装
データ構造を定義
まず、データ構造を定義します。1つのチャットメッセージはmessage
と投稿日時date
を持つようにします。それとは別にデータ上のキーを持ちます。簡単。以下のように定義しました。databaseのデータはDataSnapshot
として取得できるので、DataSnapshot
からインスタンスを生成できるようにしています。
class ChatEntry {
String key;
DateTime dateTime;
String message;
ChatEntry(this.dateTime, this.message);
ChatEntry.fromSnapShot(DataSnapshot snapshot):
key = snapshot.key,
dateTime = new DateTime.fromMillisecondsSinceEpoch(snapshot.value["date"]),
message = snapshot.value["message"];
toJson() {
return {
"date": dateTime.millisecondsSinceEpoch,
"message": message,
};
}
}
画面の実装
画面全体の実装をまず記載しておきます。
class FirebaseChatPage extends StatefulWidget {
@override
_FirebaseChatPageState createState() => new _FirebaseChatPageState();
}
class _FirebaseChatPageState extends State<FirebaseChatPage> {
final _mainReference = FirebaseDatabase.instance.reference().child("messages");
final _textEditController = TextEditingController();
List<ChatEntry> entries = new List(); // チャッtのメッセージリスト
@override
initState() {
super.initState();
_mainReference.onChildAdded.listen(_onEntryAdded);
}
_onEntryAdded(Event e) {
setState(() {
entries.add(new ChatEntry.fromSnapShot(e.snapshot));
});
}
// 画面全体のビルド
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("Firebase Chat")
),
body: Container(
child: new Column(
children: <Widget>[
Expanded(
child:
ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (BuildContext context, int index) {
return _buildRow(index);
},
itemCount: entries.length,
),
),
Divider(height: 4.0,),
Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildInputArea()
)
],
)
),
);
}
// 投稿されたメッセージの1行を表示するWidgetを生成
Widget _buildRow(int index) {
return Card(
child: ListTile(
title: Text(entries[index].message)
)
);
}
// 投稿メッセージの入力部分のWidgetを生成
Widget _buildInputArea() {
return Row(
children: <Widget>[
SizedBox(width: 16.0,),
Expanded(
child: TextField(
controller: _textEditController,
),
),
CupertinoButton(
child: Text("Send"),
onPressed: () {
_mainReference.push().set(ChatEntry(DateTime.now(), _textEditController.text).toJson());
_textEditController.clear();
// キーボードを閉じる
FocusScope.of(context).requestFocus(new FocusNode());
},
)
],
);
}
}
Realtime databaseからの受信部分
final _mainReference = FirebaseDatabase.instance.reference().child("messages"); // 1
@override
initState() {
super.initState();
_mainReference.onChildAdded.listen(_onEntryAdded); // 2
}
_onEntryAdded(Event e) { // 3
setState(() {
entries.add(new ChatEntry.fromSnapShot(e.snapshot));
});
}
- まず、databaseのルートの下の
messages
という子を参照するようにします。 messages
に子が追加されたら_onEntryAdded
をコールするように設定します。_onEntryAdded
で追加された子を取得できるのでSnapshotからChatEntry
クラスを生成してリストに追加して、画面を再描画します。
Realtime databaseへの送信部分
// 投稿メッセージの入力部分のWidgetを生成
Widget _buildInputArea() {
return Row(
children: <Widget>[
SizedBox(width: 16.0,),
Expanded(
child: TextField(
controller: _textEditController,
),
),
CupertinoButton(
child: Text("Send"),
onPressed: () {
_mainReference.push().set(ChatEntry(DateTime.now(), _textEditController.text).toJson()); // 1
_textEditController.clear();
// キーボードを閉じる
FocusScope.of(context).requestFocus(new FocusNode());
},
)
],
);
}
- 送信ボタンが押された時に、textEditControllerから入力されたテキストを取得し、現在時刻と共に
ChatEntry
クラスを生成して、送信します。
投稿した後、Firebase ConsoleでDatabaseを見ると以下のようになっていました。
ちゃんとデータが書き込まれていますね。
リストについて
今回の実装ではListView
を仕様していますが、今回使用したライブラリにはFirebaseAnimatedList
というクラスが用意されています。こちらはRealtime Databaseのリファレンスをセットしておくと、追加、更新、削除の時に自動で描画してくれる便利なクラスです。仕様に合致する場合は積極的に使って良いと思います。
最後に
いかがだったでしょうか。Flutterを触っていて詰まった時、iOSであればあたりがつくのですが、Androidの方は経験不足からなかなかどこに問題があるのかパッとイメージがつかず四苦八苦しております。これからもがんばろー。