Google は、黒人コミュニティのための人種的公平の促進に取り組んでいます。詳細をご覧ください。

FlutterのFirebaseについて知る

このコードラボでは、あなたはの基礎のいくつかを学びますFirebaseをAndroidとiOSのためのフラッターのモバイルアプリケーションを作成します。

前提条件

このコードラボでは、あなたがフラッターに精通していると仮定し、あなたがインストールしたフラッターSDK 、およびエディタを

作成するもの

このコードラボでは、Flutterを使用して、Android、iOS、Web、およびmacOSでイベントRSVPおよびゲストブックチャットアプリを構築します。 Firebase認証でユーザーを認証し、CloudFirestoreを使用してデータを同期します。

必要なもの

このコードラボは、次のデバイスのいずれかを使用して実行できます。

上記に加えて、次のものも必要になります。

  • Chromeなどの選択したブラウザ。
  • アンIDEかなど、お好みのテキストエディタ、 AndroidのメーカーまたはVSコードダーツ・フラッターのプラグインで構成。
  • 最新stableバージョンのフラッター(またはbetaあなたは、エッジで生活を楽しむ場合)。
  • Firebaseプロジェクトを作成および管理するためのGmailアカウントなどのGoogleアカウント。
  • codelabのサンプルコード。コードを取得する方法については、次の手順を参照してください。

プロジェクトの初期バージョンをGitHubからダウンロードすることから始めましょう。

クローンGitHubのリポジトリ、コマンドラインから:

git clone https://github.com/flutter/codelabs.git flutter-codelabs

また、あなたが持っている場合はGitHubでのCLIツールをインストール:

gh repo clone flutter/codelabs flutter-codelabs

サンプルコードでは、中にクローン化されなければならないflutter-codelabs 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お好みのIDEにディレクトリ。このディレクトリには、まだ機能していないFlutterミートアップアプリで構成されるcodelabの開始コードが含まれています。

作業するファイルを見つけます

このアプリのコードは複数のディレクトリに分散しています。この機能の分割は、コードを機能ごとにグループ化することにより、作業を容易にするように設計されています。

プロジェクトで次のファイルを見つけます。

  • lib/main.dart :このファイルは、メインエントリポイントとアプリケーションのウィジェットが含まれています。
  • lib/src/widgets.dart :このファイルは、アプリケーションのスタイリングを標準化する助けにウィジェットの一握りが含まれています。これらは、スターターアプリの画面を構成するために使用されます。
  • lib/src/authentication.dart :このファイルは、の部分的な実装が含まれFirebaseUI認証をFirebase電子メールベースの認証用のログイン・ユーザーエクスペリエンスを作成するためのウィジェットのセットで。認証フロー用のこれらのウィジェットは、スターターアプリではまだ使用されていませんが、すぐに接続します。

アプリケーションの残りの部分を構築するために、必要に応じてファイルを追加します。

見直しlib/main.dartファイルを

このアプリはを活用するgoogle_fonts Roboto全アプリ全体のデフォルトフォントにするために私たちを可能にするためのパッケージ。やる気の読者のための運動が探ることであるfonts.google.comを、あなたはアプリのさまざまな部分であり発見フォントを使用しています。

あなたはからヘルパーウィジェットを利用しているlib/src/widgets.dartの形でHeaderParagraphIconAndDetail 。これらのウィジェットはで説明したページレイアウトに混乱を減らすHomePage重複したコードを排除することによって。これには、一貫したルックアンドフィールを可能にするという追加の利点があります。

Android、iOS、Web、macOSでのアプリの外観は次のとおりです。

アプリのプレビュー

イベント情報を表示することはゲストにとっては素晴らしいことですが、イベントを表示するだけでは誰にとってもあまり役に立ちません。このアプリにいくつかの動的機能を追加しましょう。このためには、Firebaseをアプリに接続する必要があります。 Firebaseの使用を開始するには、Firebaseプロジェクトを作成して設定する必要があります。

Firebaseプロジェクトを作成する

  1. サインインしFirebase
  2. Firebaseコンソールで、クリックプロジェクトを追加(またはプロジェクトを作成します)、そしてあなたのFirebaseプロジェクトFirebaseフラッタ・コードラボに名前付けます。

4395e4e67c08043a.png

  1. プロジェクト作成オプションをクリックします。プロンプトが表示されたら、Firebaseの利用規約に同意します。このアプリではアナリティクスを使用しないため、Googleアナリティクスの設定はスキップしてください。

b7138cde5f2c7b61.png

Firebaseプロジェクトの詳細については、参照Firebaseプロジェクトを理解します

構築しているアプリは、ウェブアプリで利用できるいくつかのFirebase製品を使用しています。

  • Firebase認証は、ユーザーが自分のアプリに署名することを可能にします。
  • クラウド上の構造化データを保存したときにデータの変更のインスタント通知を取得するためのクラウドFirestore。
  • Firebaseセキュリティルールは、データベースを確保します。

これらの製品の一部は、特別な構成が必要であるか、Firebaseコンソールを使用して有効にする必要があります。

電子メールのサインインFirebase認証のための有効化

ユーザーがウェブアプリにログインできるようにするには、このコードラボのために電子メール/パスワードログインメソッドを使用します:

  1. Firebaseコンソールでは、左側のパネルで、[ビルド]メニューを展開します。
  2. 認証]クリックし、[取得の開始]ボタンをクリックし、サインインし、メソッドタブ(またはこちらをクリックしてくださいサインインのメソッドタブに直接移動します)。
  3. 位置でのスイッチをオンにし、[保存]クリックし、プロバイダ・サインインリストに電子メール/パスワード]クリックします。 58e3e3e23c2f16a4.png

CloudFirestoreを有効にする

ウェブアプリが使用するクラウドFirestoreをチャットメッセージを保存し、新しいチャットメッセージを受信します。

Cloud Firestoreを有効にする:

  1. Firebaseコンソールの[ビルド]セクションでは、クラウドFirestoreをクリックします。
  2. データベースの作成]クリックします。 99e8429832d23fa3.png
  1. テストモードオプションの[開始]を選択します。セキュリティルールに関する免責事項をお読みください。テストモードでは、開発中にデータベースに自由に書き込むことができます。 [次へ]クリックします。 6be00e26c72ea032.png
  1. データベースの場所を選択します(デフォルトを使用できます)。この場所は後で変更できないことに注意してください。 278656eefcfb0216.png
  2. 有効にする]をクリックします。

FlutterでFirebaseを使用するには、FlutterFireライブラリを正しく利用するようにFlutterプロジェクトを構成するプロセスに従う必要があります。

  • FlutterFireの依存関係を追加pubspec.yaml
  • 目的のプラットフォームをFirebaseプロジェクトに登録します
  • プラットフォーム固有の構成ファイルをダウンロードして、コードに追加します。

あなたのフラッタアプリのトップレベルのディレクトリでは、と呼ばれるサブディレクトリがありiosandroid 。これらのディレクトリは、それぞれiOSとAndroidのプラットフォーム固有の構成ファイルを保持します。

依存関係を構成する

このアプリで使用している2つのFirebase製品(FirebaseAuthとCloudFirestore)のFlutterFireライブラリを追加する必要があります。編集pubspec.yamlと次の依存関係を追加します。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^1.0.0 # new
  firebase_auth: ^1.0.0   # new
  google_fonts: ^2.0.0
  provider: ^5.0.0        # new

必要なパッケージを追加しましたが、Firebaseを適切に利用するようにiOS、Android、macOS、およびWebランナープロジェクトを構成する必要もあります。また、使用しているprovider表示ロジックからビジネスロジックの分離を可能にするパッケージを。

iOSを構成する

  1. Firebaseコンソール、左側のナビゲーションバーのプロジェクトの概要を選択して、あなたのアプリにFirebaseを追加することによって始める下のiOS]ボタンをクリックしてください。

次のダイアログが表示されます。

c42139f18fb9a2ee.png

  1. 提供するための重要な値は、iOSバンドルIDです次の3つの手順を実行して、バンドルIDを取得します。
  1. コマンドラインツールで、Flutterアプリの最上位ディレクトリに移動します。
  2. コマンドを実行しopen ios/Runner.xcworkspace開いたのXcodeに。
  1. Xcodeで、示すように、右側のペインで[全般]タブを表示するには、ターゲットの下でランナーを選択し、左側のペインでトップレベルのランナーをクリックしてください。バンドル識別子の値をコピーします。

9d67acd88c718763.png

  1. 、Firebaseダイアログに戻るのiOSバンドルIDフィールドにコピーバンドル識別子を貼り付け、登録アプリケーション]クリックします。
  1. Firebaseに続いて、コンフィギュレーション・ファイルダウンロードするための指示に従っGoogleService-Info.plist
  2. Xcodeに戻ります。ランナーはまた、ランナー(前の画像に示されている)と呼ばれるサブフォルダを有していることに注意してください。
  3. ドラッグGoogleService-Info.plistそのランナーのサブフォルダに(あなたがちょうどダウンロードしたことを)ファイルを。
  4. Xcodeで表示されるダイアログで、[完了]クリックします。
  5. 今後は必要ないため、この時点でXcodeを自由に閉じてください。
  6. Firebaseコンソールに戻ります。セットアップ段階では、[次へ]クリックし、残りのステップをスキップして、Firebaseコンソールのメインページに戻ります。

これで、iOS用のFlutterアプリの構成が完了しました。詳細については、を参照してくださいFlutterFire iOS版のインストールマニュアルを

Androidを構成する

  1. Firebaseコンソール、左側のナビゲーションバーのプロジェクトの概要を選択して、あなたのアプリにFirebaseを追加することによって始めるの下でのAndroidのボタンをクリックしてください。

次のダイアログが表示されます。 8254fc299e82f528.png

  1. 提供するための重要な値は、Androidパッケージ名です。次の2つの手順を実行すると、パッケージ名が取得されます。
  1. あなたのフラッタappディレクトリには、ファイルを開きandroid/app/src/main/AndroidManifest.xml
  2. manifest要素の文字列値を見つけるpackage属性を。この値は、Androidのパッケージ名(のようなものであるcom.yourcompany.yourproject )。この値をコピーします。
  3. Firebaseダイアログで、Androidのパッケージ名フィールドにコピーパッケージ名を貼り付けます。
  4. あなたはこのコードラボのデバッグ署名証明書のSHA-1を必要としません。これは空白のままにします。
  5. 登録アプリケーション]クリックします。
  6. Firebaseに続いて、コンフィギュレーション・ファイルのダウンロードに指示に従ってくださいgoogle-services.json
  7. あなたのフラッタappディレクトリに移動し、移動google-services.jsonファイルを(あなたがちょうどダウンロードしたこと)にandroid/appディレクトリ。
  8. Firebaseコンソールに戻り、残りの手順をスキップして、Firebaseコンソールのメインページに戻ります。
  9. 編集あなたのandroid/build.gradle追加するgoogle-services依存関係のプラグイン:

android / build.gradle

dependencies {
        classpath 'com.android.tools.build:gradle:4.1.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:4.3.5'  // new
}
  1. 編集あなたのandroid/app/build.gradle有効にするにはgoogle-servicesプラグイン:

android / app / build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services'  // new
  1. FirebaseではMultidexを有効にする必要があります。そのための1つの方法は、サポートされる最小のSDKを21以上に設定することです。編集あなたのandroid/app/build.gradle更新するminSdkVersion

android / app / build.gradle

defaultConfig {
    applicationId "com.example.gtk_flutter"
    minSdkVersion 21  // Updated
    targetSdkVersion 30
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
}

これで、Android用のFlutterアプリの構成が完了しました。詳細については、を参照してくださいFlutterFire Androidのインストールマニュアルを

Web用に構成する

  1. Firebaseコンソール、左側のナビゲーションバーのプロジェクトの概要を選択して、あなたのアプリにFirebaseを追加することによって始めるの下でのWebボタンをクリックします。

25b14deff9e589ce.png

  1. このアプリにニックネームを付けて、登録アプリのボタンをクリックします。このチュートリアルでは、Firebase Hostingをローカルでのみ実行するため、オフのままにしておきます。 Firebase Hostingの詳細については、こちらをご覧ください。 9c697cc1b309c806.png
  2. 編集あなたのボディセクションweb/index.htmlファイルは、以下のように。必ず追加してくださいfirebaseConfig前のステップからのデータを。

web / index.html

<body>
  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('flutter-first-frame', function () {
        navigator.serviceWorker.register('flutter_service_worker.js');
      });
    }
  </script>

  <!-- Add from here -->
  <script src="https://www.gstatic.com/firebasejs/7.20.0/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.20.0/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.20.0/firebase-firestore.js"></script>
  <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
      // Replace this with your firebaseConfig
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
  </script>
  <!-- to here. -->

  <script src="main.dart.js" type="application/javascript"></script>
</body>

これで、FlutterアプリをWeb用に構成できました。詳細については、を参照してくださいFlutterFireのWebインストールマニュアルを

macOSを構成する

macOSの設定手順はiOSとほぼ同じです。私たちは、再利用の設定ファイルに行っているGoogleService-Info.plistのiOSは、上記の手順から。

  1. コマンドを実行しopen macos/Runner.xcworkspace開いたのXcodeに。
  1. ドラッグGoogleService-Info.plistランナーサブフォルダにファイルを。これは、上記の設定iOSのステップで作成されました。 c2b9229a605fd738.png
  2. macos/Runner/DebugProfile.entitlementsファイル、追加com.apple.security.network.client権利を、そしてそれを設定しtrue8bee5665e35d3f34.png
  3. macos/Runner/Release.entitlementsファイルも追加com.apple.security.network.client権利を、そしてそれを設定しtrue41e2e23b7928546a.png
  4. 今後は必要ないため、この時点でXcodeを自由に閉じてください。

これで、macOS用のFlutterアプリの構成が完了しました。詳細については、を参照してくださいFlutterFire MacOSのインストールマニュアル、およびフラッタ用デスクトップのサポートページを。

今、あなたはアプリにFirebaseを追加したことを、あなたはレジスタの人々が使用していることをRSVPボタンに設定することができますFirebase認証を。 Androidネイティブ、iOSネイティブ、およびWebの場合、事前にビルドされたFirebaseUI Authパッケージがありますが、Flutterの場合、この機能をビルドする必要があります。

手順2で取得したプロジェクトには、ほとんどの認証フローのユーザーインターフェイスを実装する一連のウィジェットが含まれていました。 FirebaseAuthenticationをアプリケーションに統合するためのビジネスロジックを実装します。

プロバイダーとのビジネスロジック

あなたは、使用しようとしているproviderフラッタウィジェットのアプリケーションのツリー全体で集中型のアプリケーション状態オブジェクトを利用できるようにパッケージを。で開始するには、の上部に輸入を変更するlib/main.dart

lib / main.dart

import 'package:firebase_core/firebase_core.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';           // new

import 'src/authentication.dart';                  // new
import 'src/widgets.dart';

importラインはFirebase Coreと認証、プル紹介providerウィジェットツリーを介してアプリケーション状態オブジェクトを利用できるように、との認証ウィジェット含めるために使用しているパッケージlib/src

このアプリケーション状態オブジェクト、 ApplicationState 、このステップのための2つの主要な責任を持っていますが、それ以降の手順でアプリケーションに多くの機能を追加すると、追加の責任を得ることができます。最初の責任はを呼び出してFirebaseライブラリを初期化することであるFirebase.initializeApp()した後、承認フローの取り扱いがあります。最後に次のクラスを追加しますlib/main.dart

lib / main.dart

class ApplicationState extends ChangeNotifier {
  ApplicationState() {
    init();
  }

  Future<void> init() async {
    await Firebase.initializeApp();

    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();
  }

  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);
    }
  }

  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();
  }

  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!.updateProfile(displayName: 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に依存するウィジェットを再表示する際に知ってパッケージを。最後に、使用してアプリケーションの状態の統合Authentication更新することにより、 HomePagebuild方法を:

lib / main.dart

class HomePage extends StatelessWidget {
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          SizedBox(height: 8),
          IconAndDetail(Icons.calendar_today, 'October 30'),
          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
          Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          Header("What we'll be doing"),
          Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
        ],
      ),
    );
  }
}

あなたは、インスタンスAuthenticationウィジェットを、そして、それをラップConsumerウィジェット。消費者はそれを通常の方法をウィジェットproviderパッケージはツリーアプリケーションの状態の変化の一部を再構築するために使用することができます。 Authenticationあなたが今テストすることを認証UIウィジェットです。

認証フローのテスト

cdf2d25e436bd48d.png

これが認証フローの開始です。ここで、ユーザーはRSVPボタンをタップして、電子メールフォームを開始できます。

2a2cd6d69d172369.png

電子メールを入力すると、システムはユーザーがすでに登録されているかどうかを確認します。登録されている場合はパスワードの入力を求められます。登録されていない場合は、登録フォームを使用します。

e5e65065dba36b54.png

エラー処理フローを確認するには、必ず短いパスワード(6文字未満)を入力してみてください。ユーザーが登録されている場合は、代わりにのパスワードが表示されます。

fbb3ea35fb4f67a.png

このページでは、このページのエラー処理を確認するために、間違ったパスワードを入力してください。最後に、ユーザーがログインすると、ログインエクスペリエンスが表示され、ユーザーは再度ログアウトすることができます。

4ed811a25b0cf816.png

これで、認証フローが実装されました。おめでとうございます!

ユーザーが来ていることを知っているのは素晴らしいことですが、アプリでゲストに何か他のことをしてもらいましょう。ゲストブックにメッセージを残すことができたらどうなるでしょうか。彼らは、なぜ彼らが来ることに興奮しているのか、誰に会いたいのかを共有することができます。

ユーザーがアプリで書くチャットメッセージを格納するには、使用しますクラウドFirestoreを

データ・モデル

Cloud FirestoreはNoSQLデータベースであり、データベースに格納されているデータは、コレクション、ドキュメント、フィールド、およびサブコレクションに分割されます。あなたはと呼ばれるトップレベルのコレクション内の文書としてチャットの各メッセージを格納しますguestbook

7c20dc8424bb1d84.png

Firestoreにメッセージを追加する

このセクションでは、ユーザーがデータベースに新しいメッセージを書き込むための機能を追加します。最初に、UI要素(フォームフィールドと送信ボタン)を追加し、次にこれらの要素をデータベースにフックするコードを追加します。

まず、輸入のための追加cloud_firestoreパッケージとdart:async

lib / main.dart

import 'dart:async';                                    // new
import 'package:cloud_firestore/cloud_firestore.dart';  // new
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';

import 'src/authentication.dart';
import 'src/widgets.dart';

メッセージフィールドと送信ボタンのUI要素を構築するには、ウィジェットの新しいステートフル追加GuestBookの下部にあるlib/main.dart

lib / main.dart

class GuestBook extends StatefulWidget {
  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;
                },
              ),
            ),
            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'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

ここにはいくつかの興味深い点があります。まず、フォームをインスタンス化して、upiがメッセージに実際にコンテンツが含まれていることを検証し、コンテンツがない場合はユーザーにエラーメッセージを表示できるようにします。フォームを検証する方法は、フォームの後ろに、フォームの状態にアクセスすることを含む、これのためにあなたが使用GlobalKey 。キーの詳細については、それらを使用する方法を参照してください「使うキーに」フラッターウィジェット101のエピソードを

また、ウィジェットがレイアウトされている方法に注意してください、あなたが持っているRowして、 TextFormFieldStyledButton自体が含まれている、 Row 。また、注意してくださいTextFormFieldに包まれているExpandedウィジェット、この力TextFormField行の任意の余分なスペースを取るします。これが必要な理由をよりよく理解するために、一読してください理解制約

これで、ユーザーがゲストブックに追加するテキストを入力できるウィジェットができたので、それを画面に表示する必要があります。これを行うには、編集の身体HomePageの一番下に次の2行を追加するにはListViewの子:

Header("What we'll be doing"),
Paragraph(
  'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
Header('Discussion'),
GuestBook(addMessage: (String message) => print(message)),

これはウィジェットを表示するのに十分ですが、何か便利なことをするのに十分ではありません。このコードをまもなく更新して、機能させる予定です。

アプリのプレビュー

SENDボタンをクリックすると、ユーザーは以下のコードをトリガーします。これは、メッセージの入力フィールドの内容を追加してguestbookデータベースのコレクション。具体的には、 addMessageToGuestBook方法は、に(自動的に生成されたIDを持つ)新しい文書にメッセージの内容を追加guestbookコレクション。

注意FirebaseAuth.instance.currentUser.uid Firebase認証がログインしているユーザーのすべてのために与えることが自動生成されたユニークな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 {
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          SizedBox(height: 8),
          IconAndDetail(Icons.calendar_today, 'October 30'),
          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,
            ),
          ),
          Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          Header("What we'll be doing"),
          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) ...[
                  Header('Discussion'),
                  GuestBook(
                    addMessage: (String message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // To here.
        ],
      ),
    );
  }
}

このステップの開始時に追加した2行を、完全な実装に置き換えました。あなたは再び使用しているConsumer<ApplicationState>あなたがレンダリングされているツリーの一部へのアプリケーションの状態を利用できるようにします。これにより、誰かがUIにメッセージを入力したことに反応し、それをデータベースに公開できます。次のセクションでは、追加されたメッセージがデータベースに公開されているかどうかをテストします。

メッセージの送信をテストする

  1. アプリにサインインしていることを確認してください。
  2. そのような「ちょっとそこ!」とメッセージを入力し、[送信]クリックしてください。

このアクションにより、メッセージがCloudFirestoreデータベースに書き込まれます。ただし、データの取得を実装する必要があるため、実際のFlutterアプリにはまだメッセージが表示されません。次のステップでそれを行います。

ただし、Firebaseコンソールに新しく追加されたメッセージが表示されます。

Firebaseコンソールで、中にデータベースのダッシュボード、あなたが見るべきであるguestbookあなたの新しく追加されたメッセージのコレクションを。メッセージを送信し続けると、ゲストブックコレクションには次のような多くのドキュメントが含まれます。

Firebaseコンソール

713870af0b3b63c.png

ゲストがデータベースにメッセージを書き込むことができるのは素晴らしいことですが、アプリではまだメッセージを見ることができません。それを直そう!

メッセージを同期する

メッセージを表示するには、データが変更されたときにトリガーされるリスナーを追加してから、新しいメッセージを表示する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();

    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();
    });
  }

このセクションでは、のように、ここで、重要であるあなたがオーバークエリ構築ところでguestbookコレクションを、ハンドルがサブスクライブし、このコレクションに退会します。あなたは、あなたがメッセージのローカルキャッシュを再構築ストリーム、に耳を傾けるguestbookコレクション、そしてあなたが後でそれから退会することができますので、またこのサブスクリプションへの参照を格納します。ここでは多くのことが行われているので、デバッガーで時間をかけて、より明確なメンタルモデルを取得するときに何が起こるかを調べる価値があります。

詳細については、クラウドFirestoreのマニュアルを

では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 、次のような方法を。

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のメッセージのリスト内の各メッセージのために。

最後に、あなたは今の身体に更新する必要がHomePage正しく構築するためにGuestBook新たにしてmessagesパラメータ。

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
        ),
      ],
    ],
  ),
),

同期メッセージをテストする

Cloud Firestoreは、データベースにサブスクライブしているクライアントとデータを自動的かつ即座に同期します。

  1. データベースで以前に作成したメッセージは、アプリに表示されます。新しいメッセージを自由に書いてください。それらは即座に表示されるはずです。
  2. ワークスペースを複数のウィンドウまたはタブで開くと、メッセージはタブ間でリアルタイムに同期されます。
  3. (オプション)は、削除、変更、またはFirebaseコンソールのデータベースのセクションに直接新しいメッセージを追加し、手動で試すことができます。変更はUIに表示されます。

おめでとう!アプリでCloudFirestoreドキュメントを読んでいます!

アプリケーションのpレビュー

最初に、テストモードを使用するようにCloud Firestoreを設定しました。これは、データベースが読み取りと書き込みのために開かれていることを意味します。ただし、テストモードは、開発のごく初期の段階でのみ使用する必要があります。ベストプラクティスとして、アプリを開発するときにデータベースのセキュリティルールを設定する必要があります。セキュリティは、アプリの構造と動作に不可欠である必要があります。

セキュリティルールを使用すると、データベース内のドキュメントとコレクションへのアクセスを制御できます。柔軟なルール構文を使用すると、データベース全体へのすべての書き込みから特定のドキュメントの操作まで、あらゆるものに一致するルールを作成できます。

FirebaseコンソールでCloudFirestoreのセキュリティルールを記述できます。

  1. Firebaseコンソールの開発セクションでは、データベースをクリックし、[ルール]タブを選択します(またはこちらをクリックしてくださいルール]タブに直接移動します)。
  2. 次のデフォルトのセキュリティルールと、公開されているルールに関する警告が表示されます。

7767a2d2e64e7275.png

コレクションを特定する

まず、アプリがデータを書き込むコレクションを特定します。

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;
    }
  }
}

出席者の出欠確認ステータスを記録する

現在、アプリでは、イベントに興味がある場合にチャットを開始できます。また、誰かが来ているかどうかを知る唯一の方法は、チャットに投稿するかどうかです。整理して、何人の人が来るのかを知らせましょう。

アプリケーションの状態にいくつかの新しい機能を追加します。 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({'attending': true});
  } else {
    userDoc.set({'attending': false});
  }
}

更新ApplicationStateさんinitメソッドを次のように

lib / main.dart

  Future<void> init() async {
    await Firebase.initializeApp();

    // 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 = [];
          snapshot.docs.forEach((document) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'],
                message: document.data()['text'],
              ),
            );
          });
          notifyListeners();
        });
        // Add from here
        _attendingSubscription = FirebaseFirestore.instance
            .collection('attendees')
            .doc(user.uid)
            .snapshots()
            .listen((snapshot) {
          if (snapshot.data() != null) {
            if (snapshot.data()!['attending']) {
              _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: EdgeInsets.all(8.0),
          child: Row(
            children: [
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                onPressed: () => onSelection(Attending.yes),
                child: Text('YES'),
              ),
              SizedBox(width: 8),
              TextButton(
                onPressed: () => onSelection(Attending.no),
                child: Text('NO'),
              ),
            ],
          ),
        );
      case Attending.no:
        return Padding(
          padding: EdgeInsets.all(8.0),
          child: Row(
            children: [
              TextButton(
                onPressed: () => onSelection(Attending.yes),
                child: Text('YES'),
              ),
              SizedBox(width: 8),
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                onPressed: () => onSelection(Attending.no),
                child: Text('NO'),
              ),
            ],
          ),
        );
      default:
        return Padding(
          padding: EdgeInsets.all(8.0),
          child: Row(
            children: [
              StyledButton(
                onPressed: () => onSelection(Attending.yes),
                child: Text('YES'),
              ),
              SizedBox(width: 8),
              StyledButton(
                onPressed: () => onSelection(Attending.no),
                child: Text('NO'),
              ),
            ],
          ),
        );
    }
  }
}

次は、あなたが更新する必要がHomePageを利用することのビルド方法をYesNoSelection彼らが参加している場合は指名するユーザーでログイン可能に。このイベントの参加者数も表示されます。

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)
        Paragraph('1 person going')
      else
        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.
        Header('Discussion'),
        GuestBook(
          addMessage: (String 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ダッシュボードに移動します。

アプリのプレビュー

Firebaseを使用して、インタラクティブなリアルタイムWebアプリケーションを構築しました。

私たちがカバーしたこと

  • Firebase認証
  • クラウドファイヤーストア
  • Firebaseのセキュリティルール

次のステップ

  • 他のFirebase製品についてもっと知りたいですか?ユーザーがアップロードした画像ファイルを保存したいですか?または、ユーザーに通知を送信しますか?チェックアウトFirebaseのマニュアルを。 Firebase用のFlutterプラグインについて詳しく知りたいですか?チェックアウトFlutterFireの詳細については。
  • Cloud Firestoreについて詳しく知りたいですか?サブコレクションとトランザクションについて知りたいですか?頭の上のクラウドFirestoreのWebコードラボクラウドFirestoreの詳細深さに入るコードラボのために。それとも、このチェックアウトクラウドFirestoreを知るためにYouTubeのシリーズを

もっと詳しく知る

どうだった?

フィードバックをお待ちしております。 (非常に)短いフォームに必要事項を記入してくださいここに