1.始める前に
このコードラボでは、AndroidとiOS向けのFlutterモバイルアプリを作成するためのFirebaseの基本のいくつかを学びます。
前提条件
このコードラボは、 Flutterに精通しており、FlutterSDKとエディターがインストールされていることを前提としています。
作成するもの
このコードラボでは、Flutterを使用して、Android、iOS、Web、およびmacOSでイベントRSVPおよびゲストブックチャットアプリを構築します。 Firebase認証でユーザーを認証し、CloudFirestoreを使用してデータを同期します。
必要なもの
このコードラボは、次のデバイスのいずれかを使用して実行できます。
- コンピューターに接続され、開発者モードに設定されている物理デバイス(AndroidまたはiOS)。
- iOSシミュレーター。 ( Xcodeツールをインストールする必要があります。)
- Androidエミュレーター。 ( Android Studioでのセットアップが必要です。)
上記に加えて、次のものも必要になります。
- Chromeなどの選択したブラウザ。
- DartおよびFlutterプラグインで構成されたAndroidStudioやVSCodeなど、選択したIDEまたはテキストエディター。
- Flutterの最新の
stable
バージョン(または、エッジでの生活を楽しんでいる場合はbeta
版)。 - Firebaseプロジェクトを作成および管理するためのGmailアカウントなどのGoogleアカウント。
- Gmailアカウントにログインした
firebase
コマンドラインツール。 - codelabのサンプルコード。コードを取得する方法については、次の手順を参照してください。
2.サンプルコードを取得します
GitHubからプロジェクトの初期バージョンをダウンロードすることから始めましょう。
コマンドラインからGitHubリポジトリのクローンを作成します。
git clone https://github.com/flutter/codelabs.git flutter-codelabs
または、 GitHubのCLIツールがインストールされている場合:
gh repo clone flutter/codelabs flutter-codelabs
サンプルコードは、codelabsのコレクションのコードが含まれているflutter-codelabs
ディレクトリに複製する必要があります。このコードラボのコードは、 flutter-codelabs/firebase-get-to-know-flutter
ます。
flutter-codelabs/firebase-get-to-know-flutter
下のディレクトリ構造は、名前が付けられた各ステップの最後にあるべき場所の一連のスナップショットです。これはステップ2であるため、一致するファイルを見つけるのは次のように簡単です。
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
先にスキップしたい場合、またはステップの後に何かがどのように見えるかを確認したい場合は、関心のあるステップにちなんで名付けられたディレクトリを調べてください。
スターターアプリをインポートする
flutter-codelabs/firebase-get-to-know-flutter/step_02
ディレクトリを開くかインポートします。このディレクトリには、まだ機能していないFlutterミートアップアプリで構成されるcodelabの開始コードが含まれています。
作業するファイルを見つけます
このアプリのコードは複数のディレクトリに分散しています。この機能の分割は、コードを機能ごとにグループ化することにより、作業を容易にするように設計されています。
プロジェクトで次のファイルを見つけます。
-
lib/main.dart
:このファイルには、メインのエントリポイントとアプリケーションウィジェットが含まれています。 -
lib/src/widgets.dart
:このファイルには、アプリケーションのスタイルを標準化するのに役立つウィジェットがいくつか含まれています。これらは、スターターアプリの画面を構成するために使用されます。 -
lib/src/authentication.dart
:このファイルには、Firebaseメールベース認証のログインユーザーエクスペリエンスを作成するための一連のウィジェットを備えたFirebaseUIAuthの部分的な実装が含まれています。認証フロー用のこれらのウィジェットは、スターターアプリではまだ使用されていませんが、すぐに接続します。
アプリケーションの残りの部分を構築するために、必要に応じてファイルを追加します。
lib/main.dart
ファイルを確認する
このアプリはgoogle_fonts
パッケージを利用して、アプリ全体でRobotoをデフォルトのフォントにすることができます。やる気のある読者のための演習は、 fonts.google.comを探索し、アプリのさまざまな部分で見つけたフォントを使用することです。
lib/src/widgets.dart
のヘルパーウィジェットをHeader
、 Paragraph
、 IconAndDetail
の形式で利用しています。これらのウィジェットは、重複するコードを排除することにより、 HomePage
で説明されているページレイアウトの乱雑さを軽減します。これには、一貫したルックアンドフィールを可能にするという追加の利点があります。
Android、iOS、Web、macOSでのアプリの外観は次のとおりです。
アプリのプレビュー
3.Firebaseプロジェクトを作成して設定します
イベント情報を表示することはゲストにとっては素晴らしいことですが、イベントを表示するだけでは誰にとってもあまり役に立ちません。このアプリにいくつかの動的機能を追加しましょう。このためには、Firebaseをアプリに接続する必要があります。 Firebaseの使用を開始するには、Firebaseプロジェクトを作成して設定する必要があります。
Firebaseプロジェクトを作成する
- Firebaseにログインします。
- Firebaseコンソールで、[プロジェクトの追加](または[プロジェクトの作成])をクリックし、FirebaseプロジェクトにFirebase-Flutter-Codelabという名前を付けます。
- プロジェクト作成オプションをクリックします。プロンプトが表示されたら、Firebaseの利用規約に同意します。このアプリではアナリティクスを使用しないため、Googleアナリティクスの設定はスキップしてください。
Firebaseプロジェクトの詳細については、 「Firebaseプロジェクトについて理解する」をご覧ください。
作成しているアプリは、ウェブアプリで利用できるいくつかのFirebase製品を使用しています。
- ユーザーがアプリにログインできるようにするFirebase認証。
- 構造化データをクラウドに保存し、データが変更されたときに即座に通知を受け取るCloudFirestore。
- データベースを保護するためのFirebaseセキュリティルール。
これらの製品の一部は、特別な設定が必要であるか、Firebaseコンソールを使用して有効にする必要があります。
Firebase認証のメールログインを有効にする
ユーザーがWebアプリにサインインできるようにするには、このコードラボで電子メール/パスワードのサインイン方法を使用します。
- Firebaseコンソールで、左側のパネルの[ビルド]メニューを展開します。
- [認証]をクリックし、[開始]ボタンをクリックしてから、[サインイン方法]タブをクリックします(または、ここをクリックして[サインイン方法]タブに直接移動します)。
- [サインインプロバイダー]リストで[電子メール/パスワード]をクリックし、[有効にする]スイッチをオンの位置に設定して、[保存]をクリックします。
CloudFirestoreを有効にする
Webアプリは、 Cloud Firestoreを使用してチャットメッセージを保存し、新しいチャットメッセージを受信します。
CloudFirestoreを有効にする:
- Firebaseコンソールの[ビルド]セクションで、[ CloudFirestore ]をクリックします。
- [データベースの作成]をクリックします。
- [テストモードで開始]オプションを選択します。セキュリティルールに関する免責事項をお読みください。テストモードでは、開発中にデータベースに自由に書き込むことができます。 [次へ]をクリックします。
- データベースの場所を選択します(デフォルトを使用できます)。この場所は後で変更できないことに注意してください。
- [有効にする]をクリックします。
4.Firebaseの構成
FlutterでFirebaseを使用するには、FlutterFireライブラリを正しく利用するようにFlutterプロジェクトを構成するプロセスに従う必要があります。
- FlutterFireの依存関係をプロジェクトに追加します
- 目的のプラットフォームをFirebaseプロジェクトに登録します
- プラットフォーム固有の構成ファイルをダウンロードして、コードに追加します。
Flutterアプリのトップレベルのディレクトリには、 android
、 ios
、 macos
、 web
というサブディレクトリがあります。これらのディレクトリは、それぞれiOSとAndroidのプラットフォーム固有の構成ファイルを保持します。
依存関係を構成する
このアプリで使用している2つのFirebase製品(FirebaseAuthとCloudFirestore)のFlutterFireライブラリを追加する必要があります。次の3つのコマンドを実行して、依存関係を追加します。
$ flutter pub add firebase_core Resolving dependencies... + firebase_core 1.10.5 + firebase_core_platform_interface 4.2.2 + firebase_core_web 1.5.2 + flutter_web_plugins 0.0.0 from sdk flutter + js 0.6.3 test_api 0.4.3 (0.4.8 available) Changed 5 dependencies!
firebase_core
は、すべてのFirebaseFlutterプラグインに必要な共通コードです。
$ flutter pub add firebase_auth Resolving dependencies... + firebase_auth 3.3.3 + firebase_auth_platform_interface 6.1.8 + firebase_auth_web 3.3.4 + intl 0.17.0 test_api 0.4.3 (0.4.8 available) Changed 4 dependencies!
firebase_auth
は、Firebaseの認証機能との統合を可能にします。
$ flutter pub add cloud_firestore Resolving dependencies... + cloud_firestore 3.1.4 + cloud_firestore_platform_interface 5.4.9 + cloud_firestore_web 2.6.4 test_api 0.4.3 (0.4.8 available) Changed 3 dependencies!
cloud_firestore
は、CloudFirestoreデータストレージへのアクセスを有効にします。
$ flutter pub add provider Resolving dependencies... + nested 1.0.0 + provider 6.0.1 test_api 0.4.3 (0.4.8 available) Changed 2 dependencies!
必要なパッケージを追加すると同時に、Firebaseを適切に利用するようにiOS、Android、macOS、およびWebランナープロジェクトを構成する必要があります。また、ビジネスロジックを表示ロジックから分離できるようにするprovider
パッケージも使用しています。
flutterfire
インストール
FlutterFire CLIは、基盤となるFirebaseCLIに依存します。まだ行っていない場合は、 FirebaseCLIがマシンにインストールされていることを確認してください。
次に、次のコマンドを実行してFlutterFireCLIをインストールします。
$ dart pub global activate flutterfire_cli
インストールすると、 flutterfire
コマンドはグローバルに使用できるようになります。
アプリの構成
CLIは、Firebaseプロジェクトと選択したプロジェクトアプリケーションから情報を抽出して、特定のプラットフォームのすべての構成を生成します。
アプリケーションのルートで、configureコマンドを実行します。
$ flutterfire configure
構成コマンドは、いくつかのプロセスをガイドします。
- Firebaseプロジェクトの選択(.firebasercファイルまたはFirebaseコンソールから)。
- 構成するプラットフォーム(Android、iOS、macOS、Webなど)を入力します。
- 選択したプラットフォームのどのFirebaseアプリケーションを使用して構成を抽出する必要があるかを特定します。デフォルトでは、CLIは現在のプロジェクト構成に基づいてFirebaseアプリを自動的に照合しようとします。
- プロジェクトでfirebase_options.dartファイルを生成します。
macOSを構成する
macOSのFlutterは、完全にサンドボックス化されたアプリケーションを構築します。このアプリケーションはネットワークを使用してFirebaseサーバーと通信するために統合されているため、ネットワーククライアント権限を使用してアプリケーションを構成する必要があります。
macos / Runner / DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
macos / Runner / Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
詳細については、エンタイトルメントとアプリサンドボックスを参照してください。
5.ユーザーサインイン(RSVP)を追加します
アプリにFirebaseを追加したので、 Firebase認証を使用してユーザーを登録するRSVPボタンを設定できます。 Androidネイティブ、iOSネイティブ、およびWebの場合、事前にビルドされたFirebaseUI Authパッケージがありますが、Flutterの場合、この機能をビルドする必要があります。
手順2で取得したプロジェクトには、ほとんどの認証フローのユーザーインターフェイスを実装する一連のウィジェットが含まれていました。 FirebaseAuthenticationをアプリケーションに統合するためのビジネスロジックを実装します。
プロバイダーとのビジネスロジック
provider
パッケージを使用して、アプリケーションのFlutterウィジェットのツリー全体で一元化されたアプリケーション状態オブジェクトを使用できるようにします。まず、 lib/main.dart
の上部にあるインポートを変更します。
lib / main.dart
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart'; // new
import 'firebase_options.dart'; // new
import 'src/authentication.dart'; // new
import 'src/widgets.dart';
import
行では、Firebase CoreとAuthを導入し、ウィジェットツリーを介してアプリケーション状態オブジェクトを利用できるようにするために使用しているprovider
パッケージをプルし、 lib/src
からの認証ウィジェットを含めます。
このアプリケーション状態オブジェクトであるApplicationState
には、このステップに対して2つの主要な責任がありますが、後のステップでアプリケーションに機能を追加すると、追加の責任が発生します。最初の責任は、 Firebase.initializeApp()
を呼び出してFirebaseライブラリを初期化することです。その後、承認フローの処理が行われます。 lib/main.dart
の最後に次のクラスを追加します。
lib / main.dart
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
} else {
_loginState = ApplicationLoginState.loggedOut;
}
notifyListeners();
});
}
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
void startLoginFlow() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> verifyEmail(
String email,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
var methods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
if (methods.contains('password')) {
_loginState = ApplicationLoginState.password;
} else {
_loginState = ApplicationLoginState.register;
}
_email = email;
notifyListeners();
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
Future<void> signInWithEmailAndPassword(
String email,
String password,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void cancelRegistration() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> registerAccount(
String email,
String displayName,
String password,
void Function(FirebaseAuthException e) errorCallback) async {
try {
var credential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
await credential.user!.updateDisplayName(displayName);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void signOut() {
FirebaseAuth.instance.signOut();
}
}
このクラスのいくつかの重要なポイントに注意する価値があります。ユーザーは認証されていない状態で開始し、アプリはユーザーのメールアドレスを要求するフォームを表示します。そのメールアドレスが登録されているかどうかに応じて、アプリはユーザー登録を要求するか、パスワードを要求します。その後、すべてがうまくいくと仮定すると、ユーザーは認証されます。
これは、FirebaseUI Authフローの完全な実装ではないことに注意する必要があります。これは、ログインに問題がある既存のアカウントを持つユーザーのケースを処理しないためです。この追加機能の実装は、演習として残されています。やる気のある読者。
認証フローの統合
アプリケーションの状態が開始されたので、アプリケーションの状態をアプリの初期化に結び付け、認証フローをHomePage
に追加します。メインエントリポイントを更新して、 provider
パッケージを介してアプリケーションの状態を統合します。
lib / main.dart
void main() {
// Modify from here
runApp(
ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: (context, _) => App(),
),
);
// to here.
}
main
関数を変更すると、プロバイダーパッケージは、 ChangeNotifierProvider
ウィジェットを使用してアプリケーション状態オブジェクトをインスタンス化する必要があります。この特定のプロバイダークラスを使用しているのは、アプリケーション状態オブジェクトがChangeNotifier
を拡張し、 provider
パッケージが依存ウィジェットをいつ再表示するかを認識できるようにするためです。最後に、 HomePage
のbuild
メソッドを更新して、アプリケーションの状態をAuthentication
と統合します。
lib / main.dart
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.signOut,
),
),
// to here
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
],
),
);
}
}
Authentication
ウィジェットをインスタンス化し、 Consumer
ウィジェットでラップします。コンシューマウィジェットは、アプリケーションの状態が変化したときに、 provider
パッケージを使用してツリーの一部を再構築するための通常の方法です。 Authentication
ウィジェットは、これからテストする認証UIです。
認証フローのテスト
これが認証フローの開始です。ここで、ユーザーはRSVPボタンをタップして、電子メールフォームを開始できます。
電子メールを入力すると、システムはユーザーがすでに登録されているかどうかを確認します。登録されている場合はパスワードの入力を求められます。登録されていない場合は、登録フォームを使用します。
エラー処理フローを確認するには、必ず短いパスワード(6文字未満)を入力してみてください。ユーザーが登録されている場合は、代わりにのパスワードが表示されます。
このページでは、このページのエラー処理を確認するために、間違ったパスワードを入力してください。最後に、ユーザーがログインすると、ユーザーに再度ログアウトする機能を提供するログインエクスペリエンスが表示されます。
これで、認証フローが実装されました。おめでとうございます!
6.CloudFirestoreにメッセージを書き込みます
ユーザーが来ていることを知っているのは素晴らしいことですが、アプリでゲストに何か他のことをしてもらいましょう。ゲストブックにメッセージを残すことができたらどうでしょうか。彼らは、なぜ彼らが来ることに興奮しているのか、誰に会いたいのかを共有することができます。
ユーザーがアプリに書き込んだチャットメッセージを保存するには、 CloudFirestoreを使用します。
データ・モデル
Cloud FirestoreはNoSQLデータベースであり、データベースに格納されているデータは、コレクション、ドキュメント、フィールド、およびサブコレクションに分割されます。チャットの各メッセージは、 guestbook
と呼ばれるトップレベルのコレクションにドキュメントとして保存されます。
Firestoreにメッセージを追加する
このセクションでは、ユーザーがデータベースに新しいメッセージを書き込むための機能を追加します。まず、UI要素(フォームフィールドと送信ボタン)を追加し、次にこれらの要素をデータベースに接続するコードを追加します。
まず、 cloud_firestore
パッケージとdart:async
のインポートを追加します。
lib / main.dart
import 'dart:async'; // new
import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'firebase_options.dart';
import 'src/authentication.dart';
import 'src/widgets.dart';
メッセージフィールドと送信ボタンのUI要素を作成するには、 lib/main.dart
の下部に新しいステートフルウィジェットGuestBook
を追加します。
lib / main.dart
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage});
final FutureOr<void> Function(String message) addMessage;
@override
_GuestBookState createState() => _GuestBookState();
}
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return 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;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
);
}
}
ここにはいくつかの興味深い点があります。まず、フォームをインスタンス化して、メッセージに実際にコンテンツが含まれていることを検証し、コンテンツがない場合はユーザーにエラーメッセージを表示できるようにします。フォームを検証する方法には、フォームの背後にあるフォームの状態にアクセスすることが含まれます。このためには、 GlobalKey
を使用します。キーとその使用方法の詳細については、 FlutterWidgets101のエピソード「いつキーを使用するか」を参照してください。
また、ウィジェットのレイアウト方法に注意してください。 Row
があり、 TextFormField
とStyledButton
があり、それ自体にRow
が含まれています。また、 TextFormField
はExpanded
ウィジェットでラップされていることに注意してください。これにより、 TextFormField
は行内の余分なスペースを占有します。これが必要な理由をよりよく理解するには、 「制約の理解」をお読みください。
これで、ユーザーがゲストブックに追加するテキストを入力できるウィジェットができたので、それを画面に表示する必要があります。これを行うには、 HomePage
の本文を編集して、 ListView
の子の下部に次の2行を追加します。
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),
これはウィジェットを表示するのに十分ですが、何か便利なことをするのに十分ではありません。このコードをまもなく更新して、機能させる予定です。
アプリのプレビュー
ユーザーが[送信]ボタンをクリックすると、以下のコードスニペットがトリガーされます。メッセージ入力フィールドの内容をデータベースのguestbook
コレクションに追加します。具体的には、 addMessageToGuestBook
メソッドは、メッセージコンテンツを新しいドキュメント(自動生成されたIDを使用)のguestbook
コレクションに追加します。
FirebaseAuth.instance.currentUser.uid
がログインしているすべてのユーザーに提供する自動生成された一意のIDへの参照であることに注意してください。
lib/main.dart
ファイルに別の変更を加えます。 addMessageToGuestBook
メソッドを追加します。次のステップでは、ユーザーインターフェイスとこの機能を一緒に配線します。
lib / main.dart
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here
Future<DocumentReference> addMessageToGuestBook(String message) {
if (_loginState != ApplicationLoginState.loggedIn) {
throw Exception('Must be logged in');
}
return FirebaseFirestore.instance
.collection('guestbook')
.add(<String, dynamic>{
'text': message,
'timestamp': DateTime.now().millisecondsSinceEpoch,
'name': FirebaseAuth.instance.currentUser!.displayName,
'userId': FirebaseAuth.instance.currentUser!.uid,
});
}
// To here
}
UIをデータベースに配線する
ユーザーがゲストブックに追加するテキストを入力できるUIがあり、CloudFirestoreにエントリを追加するためのコードがあります。今、あなたがする必要があるのは、2つを一緒に配線することです。 lib/main.dart
で、 HomePage
ウィジェットに次の変更を加えます。
lib / main.dart
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
Consumer<ApplicationState>(
builder: (context, appState, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.signOut,
),
),
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Modify from here
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// To here.
],
),
);
}
}
このステップの開始時に追加した2行を、完全な実装に置き換えました。ここでも、 Consumer<ApplicationState>
を使用して、レンダリングしているツリーの一部でアプリケーションの状態を利用できるようにしています。これにより、UIにメッセージを入力した人に反応し、それをデータベースに公開できます。次のセクションでは、追加されたメッセージがデータベースに公開されているかどうかをテストします。
メッセージの送信をテストする
- アプリにサインインしていることを確認してください。
- 「Heythere!」などのメッセージを入力し、[送信]をクリックします。
このアクションにより、メッセージがCloudFirestoreデータベースに書き込まれます。ただし、データの取得を実装する必要があるため、実際のFlutterアプリにはまだメッセージが表示されません。次のステップでそれを行います。
ただし、Firebaseコンソールで新しく追加されたメッセージを確認できます。
Firebaseコンソールのデータベースダッシュボードに、新しく追加されたメッセージを含むguestbook
コレクションが表示されます。メッセージを送信し続けると、ゲストブックコレクションには次のような多くのドキュメントが含まれます。
Firebaseコンソール
7.メッセージを読む
ゲストがデータベースにメッセージを書き込むことができるのは素晴らしいことですが、アプリではまだメッセージを見ることができません。それを直そう!
メッセージを同期する
メッセージを表示するには、データが変更されたときにトリガーするリスナーを追加してから、新しいメッセージを表示するUI要素を作成する必要があります。アプリから新しく追加されたメッセージをリッスンするコードをアプリケーションの状態に追加します。
GuestBook
ウィジェットのすぐ上に次の値クラスがあります。このクラスは、CloudFirestoreに保存しているデータの構造化されたビューを公開します。
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.loggedOut;
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(
options: DefaultFirebaseOptions.currentPlatform,
);
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 = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
// to here.
} else {
_loginState = ApplicationLoginState.loggedOut;
// Add from here
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
notifyListeners();
});
}
このセクションは重要です。ここでは、 guestbook
コレクションに対してクエリを作成し、このコレクションのサブスクライブとサブスクライブ解除を処理します。ストリームをリッスンします。ここで、 guestbook
コレクション内のメッセージのローカルキャッシュを再構築し、このサブスクリプションへの参照を保存して、後でサブスクリプションを解除できるようにします。ここでは多くのことが行われているので、より明確なメンタルモデルを取得するときに何が起こるかを調べるためにデバッガーで時間を費やす価値があります。
詳細については、 CloudFirestoreのドキュメントを参照してください。
GuestBook
ウィジェットでは、この変化する状態をユーザーインターフェイスに接続する必要があります。ウィジェットの構成の一部としてメッセージのリストを追加することにより、ウィジェットを変更します。
lib / main.dart
class GuestBook extends StatefulWidget {
// Modify the following line
const GuestBook({required this.addMessage, required this.messages});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
_GuestBookState createState() => _GuestBookState();
}
次に、 build
メソッドを次のように変更して、この新しい構成を_GuestBookState
で公開します。
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;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
),
// Modify from here
const SizedBox(height: 8),
for (var message in widget.messages)
Paragraph('${message.name}: ${message.message}'),
const SizedBox(height: 8),
],
// to here.
);
}
}
buildメソッドの前のコンテンツをColumn
ウィジェットでラップしてから、 Column
の子の末尾にコレクションを追加して、メッセージのリスト内の各メッセージの新しいParagraph
を生成します。
最後に、新しいmessages
パラメータを使用してGuestBook
を正しく構築するために、 HomePage
の本文を更新する必要があります。
lib / main.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages, // new
),
],
],
),
),
同期メッセージをテストする
Cloud Firestoreは、データベースにサブスクライブしているクライアントとデータを自動的かつ瞬時に同期します。
- データベースで以前に作成したメッセージは、アプリに表示されます。新しいメッセージを自由に書いてください。それらはすぐに表示されるはずです。
- ワークスペースを複数のウィンドウまたはタブで開くと、メッセージはタブ間でリアルタイムに同期されます。
- (オプション) Firebaseコンソールの[データベース]セクションで、新しいメッセージを手動で直接削除、変更、または追加してみることができます。変更はUIに表示されます。
おめでとう!アプリでCloudFirestoreドキュメントを読んでいます!
アプリpレビュー
8.基本的なセキュリティルールを設定します
最初に、テストモードを使用するようにCloud Firestoreを設定しました。これは、データベースが読み取りと書き込みのために開いていることを意味します。ただし、テストモードは、開発のごく初期の段階でのみ使用する必要があります。ベストプラクティスとして、アプリを開発するときにデータベースのセキュリティルールを設定する必要があります。セキュリティは、アプリの構造と動作に不可欠である必要があります。
セキュリティルールを使用すると、データベース内のドキュメントとコレクションへのアクセスを制御できます。柔軟なルール構文を使用すると、データベース全体へのすべての書き込みから特定のドキュメントの操作まで、あらゆるものに一致するルールを作成できます。
CloudFirestoreのセキュリティルールはFirebaseコンソールで作成できます。
- Firebaseコンソールの[開発]セクションで、[データベース]をクリックし、[ルール]タブを選択します(または、ここをクリックして[ルール]タブに直接移動します)。
- 次のデフォルトのセキュリティルールと、公開されているルールに関する警告が表示されます。
コレクションを特定する
まず、アプリがデータを書き込むコレクションを特定します。
match /databases/{database}/documents
で、保護するコレクションを特定します。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
// You'll add rules here in the next step.
}
}
セキュリティルールを追加する
各ゲストブックドキュメントのフィールドとして認証UIDを使用したため、認証UIDを取得して、ドキュメントに書き込もうとしているユーザーが一致する認証UIDを持っていることを確認できます。
以下に示すように、読み取りルールと書き込みルールをルールセットに追加します。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
}
}
}
これで、ゲストブックの場合、サインインしたユーザーのみがメッセージを読むことができます(任意のメッセージ!)が、メッセージの作成者のみがメッセージを編集できます。
検証ルールを追加する
データ検証を追加して、予想されるすべてのフィールドがドキュメントに存在することを確認します。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId
&& "name" in request.resource.data
&& "text" in request.resource.data
&& "timestamp" in request.resource.data;
}
}
}
9.ボーナスステップ:学んだことを実践する
出席者の出欠確認ステータスを記録する
現在、このアプリでは、イベントに興味がある場合にチャットを開始できます。また、誰かが来ているかどうかを知る唯一の方法は、チャットに投稿するかどうかです。整理して、何人の人が来るのかを知らせましょう。
アプリケーションの状態にいくつかの新機能を追加します。 1つ目は、ログインしているユーザーが参加しているかどうかを指定する機能です。 2番目の機能は、実際に参加している人数のカウンターです。
lib/main.dart
で、アクセサーセクションに以下を追加して、UIコードがこの状態と対話できるようにします。
lib / main.dart
int _attendees = 0;
int get attendees => _attendees;
Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
final userDoc = FirebaseFirestore.instance
.collection('attendees')
.doc(FirebaseAuth.instance.currentUser!.uid);
if (attending == Attending.yes) {
userDoc.set(<String, dynamic>{'attending': true});
} else {
userDoc.set(<String, dynamic>{'attending': false});
}
}
ApplicationState
のinit
メソッドを次のように更新します。
lib / main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// Add from here
FirebaseFirestore.instance
.collection('attendees')
.where('attending', isEqualTo: true)
.snapshots()
.listen((snapshot) {
_attendees = snapshot.docs.length;
notifyListeners();
});
// To here
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
// Add from here
_attendingSubscription = FirebaseFirestore.instance
.collection('attendees')
.doc(user.uid)
.snapshots()
.listen((snapshot) {
if (snapshot.data() != null) {
if (snapshot.data()!['attending'] as bool) {
_attending = Attending.yes;
} else {
_attending = Attending.no;
}
} else {
_attending = Attending.unknown;
}
notifyListeners();
});
// to here
} else {
_loginState = ApplicationLoginState.loggedOut;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
_attendingSubscription?.cancel(); // new
}
notifyListeners();
});
}
上記は、出席者の数を確認するために常にサブスクライブされたクエリと、ユーザーが参加しているかどうかを確認するためにユーザーがログインしている間のみアクティブになる2番目のクエリを追加します。次に、 GuestBookMessage
宣言の後に次の列挙を追加します。
lib / main.dart
enum Attending { yes, no, unknown }
次に、古いラジオボタンのように機能する新しいウィジェットを定義します。はいもいいえも選択されていない不確定な状態で始まりますが、ユーザーが出席するかどうかを選択すると、そのオプションが塗りつぶされたボタンで強調表示され、他のオプションがフラットレンダリングで後退します。
lib / main.dart
class YesNoSelection extends StatelessWidget {
const YesNoSelection({required this.state, required this.onSelection});
final Attending state;
final void Function(Attending selection) onSelection;
@override
Widget build(BuildContext context) {
switch (state) {
case Attending.yes:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
TextButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
case Attending.no:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
TextButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
default:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
StyledButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
}
}
}
次に、 YesNoSelection
を利用するようにHomePage
のビルドメソッドを更新して、ログインしているユーザーが参加しているかどうかを指定できるようにする必要があります。このイベントの参加者数も表示されます。
lib / main.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Add from here
if (appState.attendees >= 2)
Paragraph('${appState.attendees} people going')
else if (appState.attendees == 1)
const Paragraph('1 person going')
else
const Paragraph('No one going'),
// To here.
if (appState.loginState == ApplicationLoginState.loggedIn) ...[
// Add from here
YesNoSelection(
state: appState.attending,
onSelection: (attending) => appState.attending = attending,
),
// To here.
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages,
),
],
],
),
),
ルールを追加する
すでにいくつかのルールが設定されているため、ボタンを使用して追加する新しいデータは拒否されます。 attendees
コレクションに追加できるようにルールを更新する必要があります。
attendees
コレクションの場合、ドキュメント名として認証UIDを使用したため、それを取得して、送信者のuid
が作成中のドキュメントと同じであることを確認できます。参加者リストは誰でも読むことができますが(プライベートデータがないため)、更新できるのは作成者だけです。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
検証ルールを追加する
データ検証を追加して、予想されるすべてのフィールドがドキュメントに存在することを確認します。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId
&& "attending" in request.resource.data;
}
}
}
(オプション)ボタンをクリックした結果を表示できるようになりました。 FirebaseコンソールでCloudFirestoreダッシュボードに移動します。
アプリのプレビュー
10.おめでとうございます!
Firebaseを使用して、インタラクティブなリアルタイムWebアプリケーションを構築しました。
私たちがカバーしたこと
- Firebase認証
- CloudFirestore
- Firebaseのセキュリティルール
次のステップ
- 他のFirebase製品についてもっと知りたいですか?ユーザーがアップロードした画像ファイルを保存したいですか?または、ユーザーに通知を送信しますか? Firebaseのドキュメントをご覧ください。 Firebase用のFlutterプラグインについて詳しく知りたいですか?詳細については、 FlutterFireを確認してください。
- Cloud Firestoreについてもっと知りたいですか?サブコレクションとトランザクションについて知りたいですか? Cloud Firestoreの詳細については、CloudFirestoreWebコードラボにアクセスしてください。または、このYouTubeシリーズをチェックして、CloudFirestoreについて理解してください。
もっと詳しく知る
- Firebaseサイト: firebase.google.com
- Flutterサイト: flutter.dev
- FlutterFire Firebase Flutterウィジェット: firebase.flutter.dev
- FirebaseYouTubeチャンネル
- フラッターYouTubeチャンネル
どうだった?
フィードバックをお待ちしております。こちらの(非常に)短いフォームに記入してください。