FirebaseUI を使用して Flutter アプリにユーザー認証フローを追加する

1. 始める前に

この Codelab では、FlutterFire UI パッケージを使用して Flutter アプリに Firebase Authentication を追加する方法を学びます。このパッケージを使用すると、メールとパスワード認証と Google ログイン認証の両方を Flutter アプリに追加できます。また、Firebase プロジェクトを設定する方法や、FlutterFire CLI を使用して Flutter アプリで Firebase を初期化する方法も学習します。

前提条件

この Codelab は、Flutter の使用経験があることを前提としています。まだの場合は、まず基本的な使い方を学習してください。次のリンクが役に立ちます。

Firebase の経験も必要ですが、Flutter プロジェクトに Firebase を追加したことがなくても問題ありません。Firebase コンソールについてよく知らない場合や、Firebase をまったく初めて使用する場合は、まず次のリンクをご覧ください。

作成するアプリの概要

この Codelab では、Firebase を認証に使用して、Flutter アプリの認証フローを構築する手順を説明します。このアプリには、ログイン画面、[登録] 画面、パスワード再設定画面、ユーザー プロフィール画面があります。

6604fc9157f2c6ae.png eab9509a41074930.png da49189a5838e0bb.png b2ccfb3632b77878.png

学習内容

この Codelab では、次のことを学びます。

  • Flutter アプリに Firebase を追加する
  • Firebase コンソールの設定
  • Firebase CLI を使用して Firebase をアプリケーションに追加する
  • FlutterFire CLI を使用して Dart で Firebase 構成を生成する
  • Flutter アプリに Firebase Authentication を追加する
  • コンソールでの Firebase Authentication の設定
  • firebase_ui_auth パッケージを使用してメールとパスワードによるログインを追加する
  • firebase_ui_auth パッケージを使用したユーザー登録の追加
  • 「パスワードをお忘れですか?」ページを追加する
  • firebase_ui_auth を使用して Google ログインを追加する
  • 複数のログイン プロバイダで動作するようにアプリを構成する。
  • firebase_ui_auth パッケージを使用してアプリケーションにユーザー プロフィール画面を追加する

この Codelab では、firebase_ui_auth パッケージを使用して堅牢な認証システムを追加することに特に重点を置いています。ご覧のとおり、上記のすべての機能を備えたこのアプリ全体を、約 100 行のコードで実装できます。

必要なもの

  • Flutter の実務知識があり、SDK がインストールされている
  • テキスト エディタ(JetBrains IDE、Android Studio、VS Code は Flutter でサポートされています)
  • Google Chrome ブラウザ、または Flutter のその他の優先開発ターゲット。(この Codelab の一部のターミナル コマンドは、Chrome でアプリを実行することを前提としています)。

2. Firebase プロジェクトを作成して設定する

最初に完了する必要があるタスクは、Firebase ウェブ コンソールで Firebase プロジェクトを作成することです。

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

  1. Google アカウントを使用して Firebase コンソールにログインします。
  2. ボタンをクリックして新しいプロジェクトを作成し、プロジェクト名(例: FlutterFire-UI-Codelab)を入力します。
  3. [続行] をクリックします。
  4. Firebase の利用規約が表示されたら、内容を読み、同意して [続行] をクリックします。
  5. (省略可)Firebase コンソールで AI アシスタンス(「Gemini in Firebase」)を有効にします。
  6. この Codelab では Google アナリティクスは必要ないため、Google アナリティクスのオプションをオフに切り替えます
  7. [プロジェクトを作成] をクリックし、プロジェクトのプロビジョニングが完了するまで待ってから、[続行] をクリックします。

Firebase プロジェクトの詳細については、Firebase プロジェクトについて理解するをご覧ください。

Firebase Authentication 用にメールログインを有効にする

作成するアプリは、Firebase Authentication を使用して、ユーザーがアプリにログインできるようにします。また、新しいユーザーが Flutter アプリケーションから登録することもできます。

Firebase Authentication は、Firebase コンソールを使用して有効にする必要があります。また、有効にしたら特別な設定を行う必要があります。

ユーザーがウェブアプリにログインできるようにするには、まずメール/パスワードのログイン方法を使用します。後で、Google ログイン メソッドを追加します。

  1. Firebase コンソールの左側のパネルで、[ビルド] メニューを開きます。
  2. [認証] をクリックし、[開始] ボタンをクリックしてから、[ログイン方法] タブをクリックします(または、[ログイン方法] タブに直接移動します)。
  3. [ログイン プロバイダ] リストで [メール/パスワード] をクリックし、[有効にする] スイッチをオンの位置に設定して、[保存] をクリックします。

58e3e3e23c2f16a4.png

3. Flutter アプリを設定する

始める前に、スターター コードをダウンロードして Firebase CLI をインストールする必要があります。

スターター コードを取得する

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

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

または、GitHub の CLI ツールがインストールされている場合は、次の操作を行います。

gh repo clone flutter/codelabs flutter-codelabs

サンプルコードのクローンは、マシン上の flutter-codelabs ディレクトリに作成する必要があります。このディレクトリには、一連の Codelab のコードが含まれています。この Codelab のコードは、サブディレクトリ flutter-codelabs/firebase-auth-flutterfire-ui にあります。

ディレクトリ flutter-codelabs/firebase-auth-flutterfire-ui には 2 つの Flutter プロジェクトが含まれています。1 つは complete、もう 1 つは start と呼ばれます。start ディレクトリには未完成のプロジェクトが含まれており、このディレクトリで最も多くの時間を費やすことになります。

cd flutter-codelabs/firebase-auth-flutterfire-ui/start

先に進みたい場合や、完成した状態を確認したい場合は、complete という名前のディレクトリで相互参照してください。

Codelab に沿って自分でコードを追加する場合は、flutter-codelabs/firebase-auth-flutterfire-ui/start の Flutter アプリから始め、Codelab 全体を通してそのプロジェクトにコードを追加する必要があります。そのディレクトリを開くか、お好みの IDE にインポートします。

Firebase CLI のインストール

Firebase CLI は、Firebase プロジェクトを管理するためのツールを提供します。CLI は FlutterFire CLI に必要です。FlutterFire CLI は後でインストールします。

CLI のインストール方法はいくつかあります。オペレーティング システムで利用可能なすべてのオプションについては、firebase.google.com/docs/cli をご覧ください。

CLI をインストールしたら、Firebase で認証する必要があります。

  1. Google アカウントで Firebase にログインするには、次のコマンドを実行します。
    firebase login
    
  2. このコマンドにより、ローカルマシンが Firebase に接続され、Firebase プロジェクトへのアクセスが許可されます。
  3. Firebase プロジェクトを一覧表示し、CLI が正しくインストールされていて、アカウントにアクセスできることをテストします。次のコマンドを実行します。
    firebase projects:list
    
  4. Firebase コンソールと同じ Firebase プロジェクトが表示されているはずです。flutterfire-ui-codelab. 以上が表示されます。

FlutterFire CLI をインストールする

FlutterFire CLI は、Flutter アプリでサポートされているすべてのプラットフォームで Firebase のインストール プロセスを簡素化するツールです。Firebase CLI をベースに構築されています。

まず、CLI をインストールします。

dart pub global activate flutterfire_cli

CLI がインストールされていることを確認します。次のコマンドを実行し、CLI がヘルプ メニューを出力することを確認します。

flutterfire --help

Firebase プロジェクトを Flutter アプリに追加する

FlutterFire を構成する

FlutterFire を使用すると、Flutter アプリで Firebase を使用するために必要な Dart コードを生成できます。

flutterfire configure

このコマンドを実行すると、使用する Firebase プロジェクトと設定するプラットフォームを選択するように求められます。

次のスクリーンショットは、回答する必要があるプロンプトを示しています。

  1. 使用するプロジェクトを選択します。この場合は、flutterfire-ui-codelab1359cdeb83204baa.png を使用します。
  2. 使用するプラットフォームを選択します。この Codelab では、ウェブ、iOS、Android 向けに Flutter 用 Firebase Authentication を構成する手順を説明しますが、すべてのオプションを使用するように Firebase プロジェクトを設定することもできます。301c9534f594f472.png
  3. このスクリーンショットは、プロセスの最後に表示される出力を示しています。Firebase に慣れている方は、コンソールでプラットフォーム アプリケーション(Android アプリケーションなど)を作成する必要がなく、FlutterFire CLI が自動的に作成したことに気づくでしょう。12199a85ade30459.png

完了したら、テキスト エディタで Flutter アプリを確認します。FlutterFire CLI が firebase_options.dart というファイルを変更しました。このファイルには、各プラットフォームに必要な Firebase 構成を保持する静的変数を持つ FirebaseOptions というクラスが含まれています。flutterfire configure の実行時にすべてのプラットフォームを選択した場合は、webandroidiosmacos という名前の静的値が表示されます。

lib/firebase_options.dart

import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
    show defaultTargetPlatform, kIsWeb, TargetPlatform;

class DefaultFirebaseOptions {
  static FirebaseOptions get currentPlatform {
    if (kIsWeb) {
      return web;
    }

    switch (defaultTargetPlatform) {
      case TargetPlatform.android:
        return android;
      case TargetPlatform.iOS:
        return ios;
      case TargetPlatform.macOS:
        return macos;
      default:
        throw UnsupportedError(
          'DefaultFirebaseOptions are not supported for this platform.',
        );
    }
  }

  static const FirebaseOptions web = FirebaseOptions(
    apiKey: 'AIzaSyCqFjCV_9CZmYeIvcK9FVy4drmKUlSaIWY',
    appId: '1:963656261848:web:7219f7fca5fc70afb237ad',
    messagingSenderId: '963656261848',
    projectId: 'flutterfire-ui-codelab',
    authDomain: 'flutterfire-ui-codelab.firebaseapp.com',
    storageBucket: 'flutterfire-ui-codelab.firebasestorage.app',
    measurementId: 'G-DGF0CP099H',
  );

  static const FirebaseOptions android = FirebaseOptions(
    apiKey: 'AIzaSyDconZaCQpkxIJ5KQBF-3tEU0rxYsLkIe8',
    appId: '1:963656261848:android:c939ccc86ab2dcdbb237ad',
    messagingSenderId: '963656261848',
    projectId: 'flutterfire-ui-codelab',
    storageBucket: 'flutterfire-ui-codelab.firebasestorage.app',
  );

  static const FirebaseOptions ios = FirebaseOptions(
    apiKey: 'AIzaSyBqLWsqFjYAdGyihKTahMRDQMo0N6NVjAs',
    appId: '1:963656261848:ios:d9e01cfe8b675dfcb237ad',
    messagingSenderId: '963656261848',
    projectId: 'flutterfire-ui-codelab',
    storageBucket: 'flutterfire-ui-codelab.firebasestorage.app',
    iosClientId: '963656261848-v7r3vq1v6haupv0l1mdrmsf56ktnua60.apps.googleusercontent.com',
    iosBundleId: 'com.example.complete',
  );

  static const FirebaseOptions macos = FirebaseOptions(
    apiKey: 'AIzaSyBqLWsqFjYAdGyihKTahMRDQMo0N6NVjAs',
    appId: '1:963656261848:ios:d9e01cfe8b675dfcb237ad',
    messagingSenderId: '963656261848',
    projectId: 'flutterfire-ui-codelab',
    storageBucket: 'flutterfire-ui-codelab.firebasestorage.app',
    iosClientId: '963656261848-v7r3vq1v6haupv0l1mdrmsf56ktnua60.apps.googleusercontent.com',
    iosBundleId: 'com.example.complete',
  );
}

Firebase では、Firebase プロジェクト内の特定のプラットフォームの特定のビルドを指すために「アプリケーション」という単語を使用します。たとえば、FlutterFire-ui-codelab という Firebase プロジェクトには、Android 用、iOS 用、macOS 用、ウェブ用の複数のアプリケーションがあります。

DefaultFirebaseOptions.currentPlatform メソッドは、Flutter によって公開された TargetPlatform 列挙型を使用して、アプリが実行されているプラットフォームを検出し、正しい Firebase アプリケーションに必要な Firebase 構成値を返します。

Flutter アプリに Firebase パッケージを追加する

設定の最後の手順は、関連する Firebase パッケージを Flutter プロジェクトに追加することです。firebase_options.dart ファイルには、まだ追加されていない Firebase パッケージに依存しているため、エラーが表示されます。ターミナルで、Flutter プロジェクトのルート(flutter-codelabs/firebase-emulator-suite/start)に移動します。次に、次の 3 つのコマンドを実行します。

flutter pub add firebase_core firebase_auth firebase_ui_auth

この時点で必要なパッケージはこれだけです。

Firebase を初期化する

追加したパッケージと DefaultFirebaseOptions.currentPlatform, を使用するには、main.dart ファイルの main 関数のコードを更新します。

lib/main.dart

import 'package:firebase_core/firebase_core.dart';                  // Add this import
import 'package:flutter/material.dart';

import 'app.dart';
import 'firebase_options.dart';                                     // And this import

// TODO(codelab user): Get API key
const clientId = 'YOUR_CLIENT_ID';

void main() async {
  // Add from here...
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  // To here.

  runApp(const MyApp(clientId: clientId));
}

このコードは次の 2 つの処理を行います。

  1. WidgetsFlutterBinding.ensureInitialized() は、Flutter フレームワークが完全に起動するまで、Flutter にアプリケーション ウィジェット コードの実行を開始しないように指示します。Firebase はネイティブ プラットフォーム チャネルを使用するため、フレームワークが実行されている必要があります。
  2. Firebase.initializeApp は、Flutter アプリと Firebase プロジェクト間の接続を設定します。DefaultFirebaseOptions.currentPlatform は、生成された firebase_options.dart ファイルからインポートされます。この静的な値は、実行中のプラットフォームを検出し、対応する Firebase キーを渡します。

4. Firebase UI Auth の初期ページを追加

Firebase UI for Auth には、アプリケーションの画面全体を表すウィジェットが用意されています。これらの画面は、ログイン、登録、パスワードを忘れた場合、ユーザー プロフィールなど、アプリケーション全体でさまざまな認証フローを処理します。まず、メイン アプリケーションの認証ガードとして機能するランディング ページをアプリに追加します。

Material アプリまたは Cupertino アプリ

FlutterFire UI では、アプリケーションが MaterialApp または CupertinoApp のいずれかでラップされている必要があります。選択内容に応じて、UI には Material ウィジェットと Cupertino ウィジェットの違いが自動的に反映されます。この Codelab では、app.dart でアプリにすでに追加されている MaterialApp を使用します。

lib/app.dart

import 'package:flutter/material.dart';

import 'auth_gate.dart';

class MyApp extends StatelessWidget {
  const MyApp({super.key, required this.clientId});

  final String clientId;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: AuthGate(clientId: clientId),
    );
  }
}

認証状態を確認する

ログイン画面を表示する前に、ユーザーが認証されているかどうかを判断する必要があります。この確認を行う最も一般的な方法は、Firebase Auth プラグインを使用して FirebaseAuthauthStateChanges をリッスンすることです。

上記のコードサンプルでは、MaterialAppbuild メソッドで AuthGate ウィジェットをビルドしています。(これは FlutterFire UI で提供されるものではなく、カスタム ウィジェットです)。

そのウィジェットは、authStateChanges ストリームを含めるように更新する必要があります。

authStateChanges API は、現在のユーザー(ログインしている場合)または null(ログインしていない場合)のいずれかを含む Stream を返します。アプリケーションでこの状態をサブスクライブするには、Flutter の StreamBuilder ウィジェットを使用して、ストリームを渡します。

StreamBuilder は、渡された Stream からのデータの最新のスナップショットに基づいて自身をビルドするウィジェットです。Stream が新しいスナップショットを出力すると、自動的に再構築されます。

auth_gate.dart のコードを更新します。

lib/auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider; // Add this import
import 'package:firebase_ui_auth/firebase_ui_auth.dart';                  // And this import
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key, required this.clientId});

  final String clientId;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(                                       // Modify from here...
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SignInScreen(providers: []);
        }

        return const HomeScreen();
      },
    );                                                                 // To here.
  }
}
  • StreamBuilder.stream には、前述のストリームである FirebaseAuth.instance.authStateChanged が渡されます。ユーザーが認証済みの場合、Firebase の User オブジェクトが返されます。それ以外の場合は null が返されます。
  • 次に、コードは snapshot.hasData を使用して、ストリームの値に User オブジェクトが含まれているかどうかを確認します。
  • ない場合は、SignInScreen ウィジェットが返されます。この画面はまだ何も行いません。次のステップで更新します。
  • それ以外の場合は、認証されたユーザーのみがアクセスできるアプリケーションのメイン部分である HomeScreen を返します。

SignInScreen は、FlutterFire UI パッケージから提供されるウィジェットです。これは、この Codelab の次のステップで扱います。この時点でアプリを実行すると、空白のログイン画面が表示されます。

5. ログイン画面

FlutterFire UI が提供する SignInScreen ウィジェットは、次の機能を追加します。

  • ユーザーがログインできるようにする
  • パスワードを忘れた場合は、[パスワードをお忘れですか?] をタップすると、パスワードを再設定するためのフォームが表示されます。
  • ユーザーがまだ登録していない場合は、[登録] をタップすると、登録できる別のフォームに移動します。

この場合も、必要なコードは数行のみです。AuthGate ウィジェットのコードを思い出してください。

lib/auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key, required this.clientId});

  final String clientId;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SignInScreen(providers: [EmailAuthProvider()]);  // Modify this line
        }

        return const HomeScreen();
      },
    );
  }
}

SignInScreen ウィジェットとその providers 引数は、前述のすべての機能を取得するために必要な唯一のコードです。「メールアドレス」と「パスワード」のテキスト入力と「ログイン」ボタンがあるログイン画面が表示されます。

機能的には問題ありませんが、スタイルが適用されていません。ウィジェットは、ログイン画面の見た目をカスタマイズするためのパラメータを公開します。たとえば、会社のロゴを追加できます。

ログイン画面をカスタマイズする

headerBuilder

SignInScreen.headerBuilder 引数を使用すると、ログイン フォームの上に任意のウィジェットを追加できます。このウィジェットは、モバイル デバイスなどの狭い画面にのみ表示されます。ワイド画面では、この Codelab で後ほど説明する SignInScreen.sideBuilder を使用できます。

次のコードで lib/auth_gate.dart ファイルを更新します。

lib/auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key, required this.clientId});

  final String clientId;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SignInScreen(                                         // Modify from here...
            providers: [EmailAuthProvider()],
            headerBuilder: (context, constraints, shrinkOffset) {
              return Padding(
                padding: const EdgeInsets.all(20),
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Image.asset('assets/flutterfire_300x.png'),
                ),
              );
            },
          );                                                           // To here.
        }

        return const HomeScreen();
      },
    );
  }
}```

The headerBuilder argument requires a function of the type HeaderBuilder, which
is defined in the FlutterFire UI package.

```dart
typedef HeaderBuilder = Widget Function(
 BuildContext context,
 BoxConstraints constraints,
 double shrinkOffset,
);

コールバックであるため、BuildContextBoxConstraints などの使用可能な値を公開し、ウィジェットを返す必要があります。返されたウィジェットは画面の上部に表示されます。この例では、新しいコードによって画面の上部に画像が追加されます。アプリケーションは次のようになります。

73d7548d91bbd2ab.png

Subtitle Builder

ログイン画面には、画面をカスタマイズできる 3 つの追加パラメータ(subtitleBuilderfooterBuildersideBuilder)が用意されています。

subtitleBuilder は、コールバック引数に AuthAction タイプの action が含まれている点が若干異なります。AuthAction は、ユーザーが現在いる画面が「ログイン」画面か「登録」画面かを検出するために使用できる列挙型です。

auth_gate.dart のコードを更新して、subtitleBuilder を使用します。

lib/auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key, required this.clientId});

  final String clientId;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SignInScreen(
            providers: [EmailAuthProvider()],
            headerBuilder: (context, constraints, shrinkOffset) {
              return Padding(
                padding: const EdgeInsets.all(20),
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Image.asset('assets/flutterfire_300x.png'),
                ),
              );
            },
            subtitleBuilder: (context, action) {                     // Add from here...
              return Padding(
                padding: const EdgeInsets.symmetric(vertical: 8.0),
                child: action == AuthAction.signIn
                    ? const Text('Welcome to FlutterFire, please sign in!')
                    : const Text('Welcome to Flutterfire, please sign up!'),
              );
            },                                                       // To here.
          );
        }

        return const HomeScreen();
      },
    );
  }
}

footerBuilder 引数は subtitleBuilder と同じです。画像ではなくテキストを対象としているため、BoxConstraintsshrinkOffset は公開されません。もちろん、任意のウィジェットを追加することもできます。

次のコードを使用して、ログイン画面にフッターを追加します。

lib/auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key, required this.clientId});

  final String clientId;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SignInScreen(
            providers: [EmailAuthProvider()],
            headerBuilder: (context, constraints, shrinkOffset) {
              return Padding(
                padding: const EdgeInsets.all(20),
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Image.asset('assets/flutterfire_300x.png'),
                ),
              );
            },
            subtitleBuilder: (context, action) {
              return Padding(
                padding: const EdgeInsets.symmetric(vertical: 8.0),
                child: action == AuthAction.signIn
                    ? const Text('Welcome to FlutterFire, please sign in!')
                    : const Text('Welcome to Flutterfire, please sign up!'),
              );
            },
            footerBuilder: (context, action) {                       // Add from here...
              return const Padding(
                padding: EdgeInsets.only(top: 16),
                child: Text(
                  'By signing in, you agree to our terms and conditions.',
                  style: TextStyle(color: Colors.grey),
                ),
              );
            },                                                       // To here.
          );
        }

        return const HomeScreen();
      },
    );
  }
}

Side Builder

SignInScreen.sidebuilder 引数はコールバックを受け取ります。今回は、そのコールバックの引数は BuildContextdouble shrinkOffset です。sideBuilder が返すウィジェットは、ログイン フォームの左側に表示され、ワイド画面でのみ表示されます。つまり、ウィジェットはパソコンとウェブアプリでのみ表示されます。

内部的には、FlutterFire UI はブレークポイントを使用して、ヘッダー コンテンツを表示するか(モバイルなどの縦長の画面)、サイド コンテンツを表示するか(パソコンやウェブなどの横長の画面)を判断します。具体的には、画面の幅が 800 ピクセルを超える場合は、サイドビルダーのコンテンツが表示され、ヘッダーのコンテンツは表示されません。画面の幅が 800 ピクセル未満の場合は、逆になります。

auth_gate.dart のコードを更新して、sideBuilder ウィジェットを追加します。

lib/auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key, required this.clientId});

  final String clientId;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SignInScreen(
            providers: [EmailAuthProvider()],
            headerBuilder: (context, constraints, shrinkOffset) {
              return Padding(
                padding: const EdgeInsets.all(20),
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Image.asset('assets/flutterfire_300x.png'),
                ),
              );
            },
            subtitleBuilder: (context, action) {
              return Padding(
                padding: const EdgeInsets.symmetric(vertical: 8.0),
                child: action == AuthAction.signIn
                    ? const Text('Welcome to FlutterFire, please sign in!')
                    : const Text('Welcome to Flutterfire, please sign up!'),
              );
            },
            footerBuilder: (context, action) {
              return const Padding(
                padding: EdgeInsets.only(top: 16),
                child: Text(
                  'By signing in, you agree to our terms and conditions.',
                  style: TextStyle(color: Colors.grey),
                ),
              );
            },
            sideBuilder: (context, shrinkOffset) {
              return Padding(
                padding: const EdgeInsets.all(20),
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Image.asset('flutterfire_300x.png'),
                ),
              );
            },
          );
        }

        return const HomeScreen();
      },
    );
  }
}

ウィンドウの幅を広げると、アプリは次のようになります(Flutter ウェブまたは MacOS を使用している場合)。

8dc60b4e5d7dd2d0.png

ユーザーを作成する

これで、この画面のコードはすべて完成しました。ただし、ログインする前にユーザーを作成する必要があります。登録画面で行うことも、Firebase コンソールでユーザーを作成することもできます。

コンソールを使用するには:

  1. Firebase コンソールの [ユーザー] テーブルに移動します。[flutterfire-ui-codelab] を選択します。別の名前を使用した場合は、別のプロジェクトを選択します。次の表が表示されます。f038fd9a58ed60d9.png
  2. [ユーザーを追加] ボタンをクリックします。2d78390d4c5dbbfa.png
  3. 新しいユーザーのメールアドレスとパスワードを入力します。下の画像のように、偽のメールアドレスとパスワードを入力してもかまいません。この方法でもアカウントを作成できますが、偽のメールアドレスを使用すると [パスワードをお忘れの場合] 機能が動作しません。62ba0feb33d54add.png
  4. [ユーザーを追加] 32b236b3ef94d4c7.png をクリックします。

これで、Flutter アプリケーションに戻り、ログインページを使用してユーザーをログインさせることができます。アプリの表示は次のようになります。

dd43d260537f3b1a.png

6. プロフィール画面

FlutterFire UI には ProfileScreen ウィジェットも用意されています。このウィジェットを使用すると、数行のコードで多くの機能を利用できます。

ProfileScreen ウィジェットを追加

テキスト エディタで home.dart ファイルに移動します。次のコードで更新します。

lib/home.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: const Icon(Icons.person),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute<ProfileScreen>(
                  builder: (context) => const ProfileScreen(),
                ),
              );
            },
          ),
        ],
        automaticallyImplyLeading: false,
      ),
      body: Center(
        child: Column(
          children: [
            SizedBox(width: 250, child: Image.asset('assets/dash.png')),
            Text('Welcome!', style: Theme.of(context).textTheme.displaySmall),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

新しいコードの注目点は、IconButton.isPressed メソッドに渡されるコールバックです。この IconButton が押されると、アプリケーションは新しい匿名ルートを作成して、そのルートに移動します。このルートには、MaterialPageRoute.builder コールバックから返された ProfileScreen ウィジェットが表示されます。

アプリを再読み込みし、右上(アプリバー内)のアイコンを押すと、次のようなページが表示されます。

36487fc4ab4f26a7.png

これは、FlutterFire UI ページで提供される標準の UI です。すべてのボタンとテキスト フィールドが Firebase Auth に接続されており、すぐに使用できます。たとえば、[名前] テキスト フィールドに名前を入力すると、FlutterFire UI が FirebaseAuth.instance.currentUser?.updateDisplayName メソッドを呼び出し、その名前が Firebase に保存されます。

ログアウト

現時点では、[ログアウト] ボタンを押してもアプリは変更されません。ログアウトはされますが、AuthGate ウィジェットに戻ることはありません。これを実装するには、ProfileScreen.actions パラメータを使用します。

まず、home.dart のコードを更新します。

lib/home.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: const Icon(Icons.person),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute<ProfileScreen>(
                  builder: (context) => ProfileScreen(
                    actions: [
                      SignedOutAction((context) {
                        Navigator.of(context).pop();
                      }),
                    ],
                  ),
                ),
              );
            },
          ),
        ],
        automaticallyImplyLeading: false,
      ),
      body: Center(
        child: Column(
          children: [
            SizedBox(width: 250, child: Image.asset('assets/dash.png')),
            Text('Welcome!', style: Theme.of(context).textTheme.displaySmall),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

これで、ProfileScreen のインスタンスを作成するときに、アクションのリストを ProfileScreen.actions 引数に渡すようになります。これらのアクションは FlutterFireUiAction 型です。FlutterFireUiAction のサブタイプであるクラスは多数存在します。一般に、これらのクラスは、さまざまな認証状態の変化に対応するようアプリに指示するために使用します。SignedOutAction は、Firebase 認証状態が currentUser が null に変わったときに、指定したコールバック関数を呼び出します。

SignedOutAction がトリガーされたときに Navigator.of(context).pop() を呼び出すコールバックを追加することで、アプリは前のページに移動します。このサンプルアプリには永続的なルートが 1 つだけあります。ログインしているユーザーがいない場合はログイン画面が表示され、ログインしているユーザーがいる場合はホームページが表示されます。ユーザーがログアウトするとこの処理が発生するため、アプリにログイン画面が表示されます。

プロフィール ページをカスタマイズする

ログイン画面と同様に、プロフィール ページもカスタマイズできます。まず、現在のページでは、ユーザーがプロフィール ページに移動すると、ホームページに戻る方法がありません。ProfileScreen ウィジェットに AppBar を追加して、この問題を修正します。

lib/home.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: const Icon(Icons.person),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute<ProfileScreen>(
                  builder: (context) => ProfileScreen(
                    appBar: AppBar(title: const Text('User Profile')),
                    actions: [
                      SignedOutAction((context) {
                        Navigator.of(context).pop();
                      }),
                    ],
                  ),
                ),
              );
            },
          ),
        ],
        automaticallyImplyLeading: false,
      ),
      body: Center(
        child: Column(
          children: [
            SizedBox(width: 250, child: Image.asset('assets/dash.png')),
            Text('Welcome!', style: Theme.of(context).textTheme.displaySmall),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

ProfileScreen.appBar 引数は Flutter Material パッケージの AppBar ウィジェットを受け取るため、作成して Scaffold に渡した他の AppBar と同様に扱うことができます。この例では、「戻る」ボタンを自動的に追加するデフォルトの機能が維持され、画面にタイトルが表示されるようになりました。

プロフィール画面にお子様を追加する

ProfileScreen ウィジェットには、children という名前のオプションの引数もあります。この引数はウィジェットのリストを受け取り、これらのウィジェットは、ProfileScreen の構築に内部で使用されている Column ウィジェット内に縦方向に配置されます。ProfileScreen ビルドメソッドのこの Column ウィジェットは、渡された子を [ログアウト] ボタンの上に配置します。

home.dart のコードを更新して、ログイン画面と同様に、ここに会社のロゴを表示します。

lib/home.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            icon: const Icon(Icons.person),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute<ProfileScreen>(
                  builder: (context) => ProfileScreen(
                    appBar: AppBar(title: const Text('User Profile')),
                    actions: [
                      SignedOutAction((context) {
                        Navigator.of(context).pop();
                      }),
                    ],
                    children: [
                      const Divider(),
                      Padding(
                        padding: const EdgeInsets.all(2),
                        child: AspectRatio(
                          aspectRatio: 1,
                          child: Image.asset('flutterfire_300x.png'),
                        ),
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
        ],
        automaticallyImplyLeading: false,
      ),
      body: Center(
        child: Column(
          children: [
            SizedBox(width: 250, child: Image.asset('assets/dash.png')),
            Text('Welcome!', style: Theme.of(context).textTheme.displaySmall),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

アプリを再読み込みすると、画面に次のように表示されます。

ebe5792b765dbf87.png

7. マルチプラットフォーム Google 認証ログイン

FlutterFire UI には、Google、Twitter、Facebook、Apple、GitHub などのサードパーティ プロバイダで認証するためのウィジェットと機能も用意されています。

Google 認証と統合するには、公式の firebase_ui_oauth_google プラグインとその依存関係をインストールします。これにより、ネイティブ認証フローが処理されます。ターミナルで、Flutter プロジェクトのルートに移動し、次のコマンドを入力します。

flutter pub add google_sign_in firebase_ui_oauth_google

Google ログイン プロバイダを有効にする

次に、Firebase コンソールで Google プロバイダを有効にします。

  1. コンソールで [認証ログイン プロバイダ] 画面に移動します。
  2. [Add new provider](新しいプロバイダを追加)をクリックします。8286fb28be94bf30.png
  3. [Google] を選択します。c4e28e6f4974be7f.png
  4. [有効にする] と表示されたスイッチを切り替え、[保存] を押します。e74ff86990763826.png
  5. 構成ファイルのダウンロードに関する情報が記載されたモーダルが表示されたら、[完了] をクリックします。
  6. Google ログイン プロバイダが追加されたことを確認します。5329ce0543c90d95.png

Google ログインボタンを追加する

Google ログインを有効にして、ログイン画面に Google ログイン ボタンのスタイル付きの表示に必要なウィジェットを追加します。auth_gate.dart ファイルに移動し、コードを次のように更新します。

lib/auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:firebase_ui_oauth_google/firebase_ui_oauth_google.dart';  // Add this import
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key, required this.clientId});

  final String clientId;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return SignInScreen(
            providers: [
              EmailAuthProvider(),
              GoogleProvider(clientId: clientId),                         // Add this line
            ],
            headerBuilder: (context, constraints, shrinkOffset) {
              return Padding(
                padding: const EdgeInsets.all(20),
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Image.asset('assets/flutterfire_300x.png'),
                ),
              );
            },
            subtitleBuilder: (context, action) {
              return Padding(
                padding: const EdgeInsets.symmetric(vertical: 8.0),
                child: action == AuthAction.signIn
                    ? const Text('Welcome to FlutterFire, please sign in!')
                    : const Text('Welcome to Flutterfire, please sign up!'),
              );
            },
            footerBuilder: (context, action) {
              return const Padding(
                padding: EdgeInsets.only(top: 16),
                child: Text(
                  'By signing in, you agree to our terms and conditions.',
                  style: TextStyle(color: Colors.grey),
                ),
              );
            },
            sideBuilder: (context, shrinkOffset) {
              return Padding(
                padding: const EdgeInsets.all(20),
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Image.asset('flutterfire_300x.png'),
                ),
              );
            },
          );
        }

        return const HomeScreen();
      },
    );
  }
}

ここで追加された新しいコードは、SignInScreen ウィジェット構成への GoogleProvider(clientId: "YOUR_WEBCLIENT_ID") の追加のみです。

追加したら、アプリを再読み込みすると、Google ログイン ボタンが表示されます。

aca71a46a011bfb5.png

ログインボタンを構成する

このボタンは、追加の設定なしでは機能しません。Flutter Web で開発している場合は、この手順を追加するだけで動作します。他のプラットフォームでは追加の手順が必要になります。これについては後ほど説明します。

  1. Firebase コンソールで [認証プロバイダ] ページに移動します。
  2. Google プロバイダをクリックします。9b3a325c5eca6e49.png
  3. [ウェブ SDK 構成] 展開パネルをクリックします。
  4. [ウェブ クライアント ID] の値をコピーします。711a79f0d931c60f.png
  5. テキスト エディタに戻り、この ID を clientId という名前のパラメータに渡して、ファイル auth_gate.dartGoogleProvider のインスタンスを更新します。
GoogleProvider(
   clientId: "YOUR_WEBCLIENT_ID"
)

ウェブ クライアント ID を入力したら、アプリを再読み込みします。[Google でログイン] ボタンを押すと、ウェブを使用している場合は新しいウィンドウが表示され、Google ログイン フローが案内されます。最初は次のようになります。

14e73e3c9de704bb.png

iOS の構成

iOS でこの機能を動作させるには、追加の設定プロセスが必要です。

  1. Firebase コンソールで [プロジェクトの設定] 画面に移動します。次のような Firebase アプリの一覧が表示されます。fefa674acbf213cc.png
  2. [iOS] を選択します。アプリケーション名は、スクリーンショットに表示されているものとは異なります。この Codelab で flutter-codelabs/firebase-auth-flutterfire-ui/start プロジェクトを使用した場合は、スクリーンショットの「complete」が「start」になります。
  3. GoogleServices-Info.plist と表示されているボタンをクリックして、必要な構成ファイルをダウンロードします。f89b3192871dfbe3.png
  4. ダウンロードしたファイルを Flutter プロジェクトの /ios/Runner というディレクトリにドラッグします。
  5. プロジェクトのルートから次のターミナル コマンドを実行して、Xcode を開きます。open ios/Runner.xcworkspace
  6. Runner ディレクトリを右クリックして、[Add Files to "Runner"] を選択します。858986063a4c5201.png
  7. ファイル マネージャーで GoogleService-Info.plist を選択します。
  8. (Xcode 以外の)テキスト エディタに戻り、以下の CFBundleURLTypes 属性を ios/Runner/Info.plist ファイルに追加します。
    <!-- Put me in the [my_project]/ios/Runner/Info.plist file -->
    <!-- Google Sign-in Section -->
    <key>CFBundleURLTypes</key>
    <array>
      <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <!-- TODO Replace this value: -->
          <!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
          <string>com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn</string>
        </array>
      </dict>
    </array>
    <!-- End of the Google Sign-in Section -->
    
  9. ウェブ設定で追加した GoogleProvider.clientId を、Firebase iOS クライアント ID に関連付けられたクライアント ID に置き換える必要があります。まず、この ID は firebase_options.dart ファイルの iOS 定数の一部として確認できます。iOSClientId に渡された値をコピーします。
    static const FirebaseOptions ios = FirebaseOptions(
      apiKey: 'YOUR API KEY',
      appId: 'YOUR APP ID',
      messagingSenderId: '',
      projectId: 'PROJECT_ID',
      storageBucket: 'PROJECT_ID.firebasestorage.app',
      iosClientId: 'IOS CLIENT ID', // Find your iOS client Id here.
      iosBundleId: 'com.example.BUNDLE',
    );
    
  10. この値を lib/main.dart ファイルの clientId 変数に貼り付けます。

lib/main.dart

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

import 'app.dart';
import 'firebase_options.dart';

const clientId = 'YOUR_CLIENT_ID'; // Replace this value with your Client ID.

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  runApp(const MyApp(clientId: clientId));
}

Flutter アプリがすでに iOS で実行されている場合は、完全にシャットダウンしてから、アプリケーションを再度実行する必要があります。それ以外の場合は、iOS でアプリを実行します。

8. 完了

Firebase Auth UI for Flutter の Codelab を完了しました。この Codelab の完成版のコードは、GitHub の firebase-auth-flutterfire-ui/complete ディレクトリにあります。

学習した内容

  • Firebase を使用するように Flutter アプリを設定する
  • Firebase コンソールで Firebase プロジェクトを設定する
  • FlutterFire CLI
  • Firebase CLI
  • Firebase Authentication の使用
  • FlutterFire UI を使用して Flutter アプリで Firebase 認証を処理する

次のステップ

詳細

Sparky がお祝いに駆けつけました。

2a0ad195769368b1.gif