2020/11/4 Get to know Firebase for Flutterの訳パート1


1. Before you begin

このコードラボでは、AndroidおよびiOS用のFlutterモバイルアプリを作成するためのFirebaseの基本のいくつかを学習します。

前提条件

このコードラボは、Flutterにある程度精通しており、FlutterSDKとエディターがインストールされていることを前提としています。


What you’ll create

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


必要なもの

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

(1)コンピューターに接続され、開発者モードに設定されている物理デバイス(AndroidまたはiOS)。
(2)iOSシミュレーター。 (Xcodeツールをインストールする必要があります。)
(3)Androidエミュレーター。 (Android Studioでのセットアップが必要です。)

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

Chromeなどの選択したブラウザ。
DartおよびFlutterプラグインで構成されたAndroidStudioやVSCodeなど、選択したIDEまたはテキストエディター。
Flutterの最新の安定バージョン(または、エッジでの生活を楽しんでいる場合はベータ版)。 FlutterFireはWebとmacOSもサポートしていますが、これらのターゲットはこのコードラボではカバーされていません。
Firebaseプロジェクトを作成および管理するためのGmailアカウントなどのGoogleアカウント。
codelabのサンプルコード。 コードを取得する方法については、次の手順を参照してください。


2. Get the sample code

コマンドラインからGitHubリポジトリのクローンを作成します。

git clone https://github.com/flutter/codelabs.git flutter-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ディレクトリがあるディレクトリに行き、↑のコマンドを実行するとステップ2の状態のプロジェクトがあります、ということ。

スキップしたい場合、またはステップの後に何かがどのように見えるかを確認したい場合は、関心のあるステップにちなんで名付けられたディレクトリを調べてください。


Import the starter app

flutter-codelabs / firebase-get-to-know-flutter / step_02ディレクトリを開くかインポートします。 このディレクトリには、まだ機能していないFlutterミートアップアプリで構成されるcodelabの開始コードが含まれています。

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

ファイル構成は次のようになっています。

lib / main.dart:このファイルには、メインのエントリポイントとアプリケーションウィジェットが含まれています。

lib / src / widgets.dart:このファイルには、アプリケーションのスタイルを標準化するのに役立つウィジェットがいくつか含まれています。 これらは、スターターアプリの画面を構成するために使用されます。

lib / src / authentication.dart:このファイルには、Firebaseメールベース認証のログインユーザーエクスペリエンスを作成するためのウィジェットのセットを備えたFirebaseUIAuthの部分的な実装が含まれています。 認証フロー用のこれらのウィジェットは、スターターアプリではまだ使用されていませんが、すぐに接続します。

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

このアプリは、google_fontsパッケージを利用して、アプリ全体でRobotoをデフォルトのフォントにすることができます。 やる気のある読者のための演習は、fonts.google.comを探索し、アプリのさまざまな部分で見つけたフォントを使用することです。

lib / src / widgets.dartにheader、paragraph、IconAndDetailなどのヘルパーウィジェットが用意されているので、これらを利用します。

これらのウィジェットは、重複するコードを排除することにより、ホームページで説明されているページレイアウトの乱雑さを軽減します。 これには、一貫したルックアンドフィールを可能にするという追加の利点があります。


3. Create and set up a Firebase project

工事中🏗

 


4. Firebase configuration

iOSはこちら

Androidはこちら

をご覧ください。




5. Add user sign-in (RSVP)

アプリにFirebaseを追加したので、Firebase認証を使用してユーザーを登録するRSVPボタンを設定できます。

Androidネイティブ、iOSネイティブ、およびWebの場合、事前にビルドされたFirebaseUI Authパッケージがありますが、Flutterの場合、この機能を自分で作る必要があります。

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


(1)Business Logic with Provider

providerパッケージを使用して、一元化されたアプリケーション状態(state)オブジェクトをアプリケーションのFlutterウィジェットのツリー全体で使用できるようにします。 まず、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';

improt文で、Firebase CoreとAuthを導入し、ウィジェットツリーを介してアプリケーション状態オブジェクトを利用できるようにするために使用するproviderパッケージをプルし、lib / srcからのauthenticationウィジェットを含めます。

このアプリケーション状態オブジェクトであるApplicationStateには、このステップに対して2つの主な責任がありますが、後のステップでアプリケーションに機能を追加すると、追加の責任が発生します。

最初の責任は、Firebase.initializeApp()を呼び出してFirebaseライブラリを初期化することです。その後、承認フローの処理が行われます。 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 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フローの完全な実装ではないことに注意する必要があります。これは、ログインに問題がある既存のアカウントを持つユーザーのケースを処理しないためです。この追加機能の実装は、読者のための演習として残しておきます。


(2)Integrating the Authentication flow

ApplicationStateクラスを定義したので、アプリケーションの状態をアプリの初期化に結び付け、認証フローをホームページに追加します。 メインエントリポイントを更新して、providerパッケージを介してアプリケーションの状態を統合します。

lib/main.dart

void main() {
  // Modify from here
  runApp(
    ChangeNotifierProvider(
      create: (context) => ApplicationState(),
      builder: (context, _) => App(),
    ),
  );
  // to here.
}

main関数を上記のように変更すると、providerパッケージは、ChangeNotifierProviderウィジェットを使用してアプリケーション状態オブジェクトをインスタンス化することが可能になります。

この特定のProviderクラスを使用しているのは、アプリケーション状態オブジェクトがChangeNotifierを継承しているからです。Providerパッケージが依存しているウィジェットをいつ再表示するかを認識できるようにするためです。HomePageのbuildメソッドを下記のように書き直して、アプリケーションの状態を認証と統合します。

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ウィジェットでラップします。 Consumerウィジェットの使用は、アプリケーションの状態が変化したときにProviderパッケージを使用してツリーの一部を再構築できる通常の方法です。 Authenticationウィジェットは、これからテストする認証UIです。


(3)Testing the Authentication flow

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


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


↑エラー処理フローを確認するには、必ず短いパスワード(6文字未満)を入力してみてください。

ユーザーが登録されている場合は、代わりにのパスワードが表示されます。


  

↑このページでは、このページのエラー処理を確認するために、間違ったパスワードを入力してください。

最後に、ユーザーがログインすると、ログインエクスペリエンスが表示され、ユーザーは再度ログアウトすることができます。

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




6. Write messages to Cloud Firestore

ユーザーが来ていることを知っているのは素晴らしいことですが、アプリでゲストに何か他のことをしてもらいましょう。

ゲストブックにメッセージを残すことができたらどうでしょうか。 彼らは、なぜ彼らが来ることに興奮しているのか、誰に会いたいのかを共有することができます。

ユーザーがアプリに書き込んだチャットメッセージを保存するには、CloudFirestoreを使用します。


(1)Data model

Cloud FirestoreはNoSQLデータベースであり、データベースに格納されているデータは、コレクション、ドキュメント、フィールド、およびサブコレクションに分割されます。

チャットの各メッセージは、guestbookと呼ばれるトップレベルのコレクションにドキュメントとして保存されます。

 

b447950f9f993789.png

ヒント:Cloud Firestoreデータモデルの詳細については、CloudFirestoreドキュメントのドキュメントとコレクションについてお読みください。 また、Cloud Firestore NoSQLデータモデル、クエリ、およびCloudFirestoreで実行できるその他の優れた機能について説明する一連のすばらしいビデオを見ることができます。


(2)Add messages to Firestore

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

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

lib/main.dart

class GuestBook extends StatefulWidget {
  GuestBook({@required this.addMessage});
  final Future<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.isEmpty) {
                    return 'Enter your message to continue';
                  }
                  return null;
                },
              ),
            ),
            SizedBox(width: 8),
            StyledButton(
              child: Row(
                children: [
                  Icon(Icons.send),
                  SizedBox(width: 4),
                  Text('SEND'),
                ],
              ),
              onPressed: () async {
                if (_formKey.currentState.validate()) {
                  await widget.addMessage(_controller.text);
                  _controller.clear();
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}

ここにはいくつかの興味深い点があります。

まず、フォームをインスタンス化して、メッセージに実際にコンテンツが含まれていることをupiが検証し、コンテンツがない場合はユーザーにエラーメッセージを表示できるようにします。

フォームを検証する方法には、フォームの背後にあるフォームの状態(FormState)にアクセスすることが含まれます。このために、GlobalKeyを使用します。 キーとその使用方法の詳細については、Flutter Widgets101のエピソード「いつキーを使用するか」を参照してください。

また、ウィジェットのレイアウト方法にも注意してください。

TextFormFieldとStyledButtonを持つRowがあり、それ自体にRowが含まれています。

また、TextFormFieldはExpandedウィジェットでラップされていることに注意してください。これにより、TextFormFieldは行内の余分なスペースを占有します。 これが必要な理由をよりよく理解するには、「制約の理解」をお読みください

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

Header('Discussion'),
GuestBook(addMessage: (String message) => print(message)),

これはウィジェットを表示するのに十分ですが、何か便利なことをするのに十分ではありません。 このコードはまもなく置き換えられます。

App preview


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

FirebaseAuth.instance.currentUser.uidは、FirebaseAuthenticationがすべてのログインユーザーに提供する自動生成された一意のIDへの参照であることに注意してください。

lib /main.dartファイルにさらに2つの変更を加えます。 まず、cloud_firestoreのインポートを追加します。

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

2つ目は、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({
      'text': message,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
      'name': FirebaseAuth.instance.currentUser!.displayName,
      'userId': FirebaseAuth.instance.currentUser!.uid,
    });
  }
  // To here
}

 


(3)Wiring the UI into the database

ユーザーがguestbookに追加したいテキストを入力できる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にメッセージを入力した人に反応し、それをデータベースに公開できます。 次のセクションでは、追加されたメッセージがデータベースに公開されているかどうかをテストします。


(4)Test sending messages

アプリにサインインしていることを確認してください。
「Heythere!」などのメッセージを入力し、[SEND]をクリックします。

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

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

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

713870af0b3b63c.png

次のページへ>>

 

 

 

参考

https://firebase.google.com/learn/codelabs/firebase-get-to-know-flutter#0

コメントを残す

メールアドレスが公開されることはありません。