使用 FirebaseUI 在 Flutter 應用程式中新增使用者驗證流程

1. 事前準備

在本程式碼研究室中,您將瞭解如何使用 FlutterFire UI 套件將 Firebase 驗證新增至 Flutter 應用程式。透過這個套件,您會將電子郵件/密碼驗證和 Google 登入驗證新增至 Flutter 應用程式。您會學到如何設定 Firebase 專案,以及使用 FlutterFire CLI 在 Flutter 應用程式中初始化 Firebase。

事前準備

本程式碼研究室假設您具備一些 Flutter 相關經驗。如果您還不熟悉,建議您先瞭解基本概念。以下連結相當實用:

您也應具備一些 Firebase 使用經驗,但如果您從未將 Firebase 新增至 Flutter 專案,也沒關係。如果您對 Firebase 控制台不熟悉,或是完全不熟悉 Firebase,請先查看下列連結:

您要建立的內容

本程式碼研究室會引導您使用 Firebase 驗證功能,為 Flutter 應用程式建立驗證流程。應用程式將包含登入畫面、「註冊」畫面、密碼復原畫面和使用者個人資料畫面。

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

課程內容

本程式碼研究室涵蓋以下內容:

  • 將 Firebase 新增至 Flutter 應用程式
  • Firebase 控制台設定
  • 使用 Firebase CLI 將 Firebase 新增至應用程式
  • 使用 FlutterFire CLI 在 Dart 中產生 Firebase 設定
  • 在 Flutter 應用程式中新增 Firebase 驗證機制
  • 在控制台中設定 Firebase 驗證
  • 使用 firebase_ui_auth 套件新增電子郵件和密碼登入功能
  • 正在透過 firebase_ui_auth 套件新增使用者註冊
  • 新增「忘記密碼?」頁面
  • 正在新增「firebase_ui_auth」的 Google 登入功能
  • 設定應用程式以支援多個登入服務供應器。
  • 使用 firebase_ui_auth 套件將使用者個人資料畫面新增至應用程式

本程式碼研究室將著重於使用 firebase_ui_auth 套件新增完善的驗證系統。如您所見,您可以使用上述所有功能,在整個應用程式中約使用 100 行程式碼實作。

事前準備

  • 熟悉 Flutter 和已安裝的 SDK
  • 文字編輯器 (Flutter 支援 JetBrains IDE、Android Studio 和 VS Code)
  • Google Chrome 瀏覽器或其他偏好的 Flutter 開發目標。(本程式碼研究室中的部分終端機指令會假設您在 Chrome 上執行應用程式)

2. 建立及設定 Firebase 專案

您需要先在 Firebase 的網路控制台中建立 Firebase 專案。

建立 Firebase 專案

  1. 登入 Firebase
  2. 在 Firebase 控制台中,按一下「新增專案」 (或「建立專案」),然後輸入 Firebase 專案名稱 (例如「FlutterFire-UI-Codelab」)。

df42a5e3d9584b48.png

  1. 點選專案建立選項。當系統顯示提示時,請接受 Firebase 條款。略過設定 Google Analytics,因為這個應用程式不會使用 Analytics。

d1fcec48bf251eaa.png

如要進一步瞭解 Firebase 專案,請參閱「瞭解 Firebase 專案」一文。

為 Firebase 驗證啟用電子郵件登入功能

您建構的應用程式採用 Firebase 驗證,可讓使用者登入您的應用程式。此外,您所建立的應用程式也會允許新使用者從 Flutter 應用程式註冊。

Firebase 驗證功能必須透過 Firebase 控制台啟用,且啟用後需要進行特殊設定。

如要允許使用者登入網頁應用程式,您必須先使用電子郵件/密碼登入方式。稍後,您將新增 Google 登入方法。

  1. 在 Firebase 控制台中,展開左側面板中的「Build」選單。
  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 目錄中,當中包含一系列程式碼研究室的程式碼。本程式碼研究室的程式碼位於 flutter-codelabs/firebase-auth-flutterfire-ui 子目錄中。

目錄 flutter-codelabs/firebase-auth-flutterfire-ui 包含兩個 Flutter 專案。其中一個稱為 complete,另一個則是 startstart 目錄包含未完成的專案,您將在此處花費最多時間。

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

如要略過前面的內容,或查看完成後的內容應有的樣貌,請在名為 complete 的目錄中查看交叉參照。

如果您想按照程式碼研究室的說明進行操作,並自行新增程式碼,請先從 flutter-codelabs/firebase-auth-flutterfire-ui/start 的 Flutter 應用程式開始,然後在程式碼研究室中為該專案新增程式碼。開啟該目錄或將其匯入至您偏好的 IDE。

安裝 Firebase CLI

Firebase CLI 提供可用來管理 Firebase 專案的工具。FlutterFire CLI 需要 CLI,您稍後會安裝這個工具。

安裝 CLI 的方法有很多種。如果您使用的是 MacOS 或 Linux,最簡單的方法就是在終端機執行下列指令:

curl -sL https://firebase.tools | bash

安裝 CLI 後,您必須透過 Firebase 進行驗證。

  1. 執行下列指令,使用 Google 帳戶登入 Firebase:
firebase login
  1. 這個指令會將本機電腦連結至 Firebase,並授予您 Firebase 專案的存取權。
  1. 請列出 Firebase 專案,測試 CLI 是否已正確安裝,且有權存取您的帳戶。執行下列指令:
firebase projects:list
  1. 畫面中顯示的清單應與 Firebase 控制台中列出的 Firebase 專案相同。您應該會看到至少 flutterfire-ui-codelab.

安裝 FlutterFire CLI

FlutterFire CLI 是一項以 Firebase CLI 為基礎建構的工具,可協助您簡化 Flutter 應用程式支援平台上的 Firebase 安裝程序。

首先,請安裝 CLI:

dart pub global activate flutterfire_cli

確認已安裝 CLI。執行下列指令,並確保 CLI 輸出說明選單。

flutterfire -—help

將 Firebase 專案新增至 Flutter 應用程式

設定 FlutterFire

您可以使用 FlutterFire 產生所需的 Dart 程式碼,以便在 Flutter 應用程式中使用 Firebase。

flutterfire configure

執行這個指令時,系統會提示您選取要使用的 Firebase 專案,以及想要設定的平台。

以下螢幕截圖顯示您需要回答的提示。

  1. 選取要使用的專案。在本例中,請使用 flutterfire-ui-codelab 1359cdeb83204baa.png
  2. 選取要使用的平台。在本程式碼研究室中,您需要為 Flutter 設定 Firebase 驗證,針對網頁、iOS 和 Android 進行設定。不過,您可以為 Firebase 專案設定採用所有選項。301c9534f594f472.png
  3. 這張螢幕截圖顯示程序結束時的輸出內容。如果您熟悉 Firebase,就會發現您不必在主控台中建立平台應用程式 (例如 Android 應用程式),FlutterFire CLI 會代為完成這項操作。12199a85ade30459.png

完成後,請在文字編輯器中查看 Flutter 應用程式。FlutterFire CLI 已產生名為 firebase_options.dart 的新檔案。這個檔案包含名為 FirebaseOptions 的類別,其中含有靜態變數,可保留各平台所需的 Firebase 設定。如果您在執行 flutterfire configure 時選取所有平台,就會看到名為 webandroidiosmacos 的靜態值。

firebase_options.dart

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

/// Default [FirebaseOptions] for use with your Firebase apps.
///
/// Example:
/// ```dart
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
///   options: DefaultFirebaseOptions.currentPlatform,
/// );
/// ```
class DefaultFirebaseOptions {
 static FirebaseOptions get currentPlatform {
   if (kIsWeb) {
     return web;
   }
   // ignore: missing_enum_constant_in_switch
   switch (defaultTargetPlatform) {
     case TargetPlatform.android:
       return android;
     case TargetPlatform.iOS:
       return ios;
     case TargetPlatform.macOS:
       return macos;
   }

   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-codelabs/firebase-emulator-suite/start 的 Flutter 專案根目錄。接著,請執行下列三個指令:

flutter pub add firebase_core
flutter pub add firebase_auth
flutter pub add firebase_ui_auth

目前您只需要這些套件。

初始化 Firebase

為了使用新增的套件,DefaultFirebaseOptions.currentPlatform, 會更新 main.dart 檔案中 main 函式的程式碼。

main.dart

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


 runApp(const MyApp());
}

這個程式碼會執行兩項作業。

  1. WidgetsFlutterBinding.ensureInitialized() 會指示 Flutter 在 Flutter 架構完全啟動前,開始執行應用程式小工具程式碼。Firebase 使用需要執行架構的原生平台管道。
  2. Firebase.initializeApp 會設定 Flutter 應用程式和 Firebase 專案之間的連結。DefaultFirebaseOptions.currentPlatform 是從產生的 firebase_options.dart 檔案匯入。這個靜態值會偵測您所使用的平台,並傳入對應的 Firebase 金鑰。

4. 新增初始 Firebase UI 驗證頁面

Firebase 驗證機制的 Firebase UI 提供代表應用程式整個畫面的小工具。這些畫面會處理應用程式中的不同驗證流程,例如登入、註冊、忘記密碼、使用者個人資料等。如要開始使用,請在應用程式中加入到達網頁,做為主要應用程式驗證防護機制。

Material Design 或 Cupertino 應用程式

FlutterFire UI 要求應用程式必須包裝在 MaterialApp 或 CupertinoApp 中。視您的選擇而定,UI 會自動反映 Material 或 Cupertino 小工具的差異。在本程式碼研究室中,請使用已新增至 app.dart 應用程式的 MaterialApp

app.dart

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

class MyApp extends StatelessWidget {
 const MyApp({super.key});
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     theme: ThemeData(
       primarySwatch: Colors.blue,
     ),
     home: const AuthGate(),
   );
 }
}

檢查驗證狀態

您必須先判斷使用者目前是否已通過驗證,才能顯示登入畫面。最常見的檢查方式是使用 Firebase Auth 外掛程式監聽 FirebaseAuth 的 authStateChanges。

在上述程式碼範例中,MaterialApp 會在建構方法中建構 AuthGate 小工具。(這是自訂小工具,並非由 FlutterFire UI 提供)。

該小工具需要更新,才能加入 authStateChanges 資料流。

authStateChanges API 會傳回含有目前使用者 (若登入) 的 Stream,如果沒有目前使用者,則會傳回空值。如要在應用程式中訂閱此狀態,可以使用 Flutter 的 StreamBuilder 小工具,並將串流傳給它。

StreamBuilder 是一種小工具,可根據您傳遞的Stream 中最新的資料快照來建構自身。當串流發出新快照時,系統會自動重新建構。

更新 auth_gate.dart 中的程式碼。

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

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

        return const HomeScreen();
      },
    );
  }
}
  • StreamBuilder.stream 會傳遞 FirebaseAuth.instance.authStateChanged,也就是上述串流,如果使用者已通過驗證,系統會傳回 Firebase User 物件。(否則會傳回 null)。
  • 接著,程式碼會使用 snapshot.hasData 檢查串流中的值是否包含 User 物件。
  • 如果沒有,則會傳回 SignInScreen 小工具。目前,這個畫面不會執行任何操作。我們會在下一個步驟中更新這項資訊。
  • 否則,則會傳回 HomeScreen,這是應用程式的主要部分,只有經過驗證的使用者可以存取。

SignInScreen 是來自 FlutterFire UI 套件的小工具。這會是本程式碼研究室下一個步驟的重點。這時當您執行應用程式時,應會看到空白的登入畫面。

5. 登入畫面

FlutterFire UI 提供的 SignInScreen 小工具會新增下列功能:

  • 允許使用者登入
  • 如果使用者忘記密碼,可以輕觸「忘記密碼?」,進入重設密碼的表單。
  • 如果尚未註冊,使用者可以輕觸「報名」,這樣使用者就能進入其他表單進行註冊。

同樣地,這只需要幾行程式碼。回想 AuthGate 小工具中的程式碼:

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

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

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

SignInScreen 小工具及其 providers 引數,是取得上述所有功能所需的唯一程式碼。您現在應該會看到登入畫面,其中包含「電子郵件」和「密碼」文字輸入欄位,以及「登入」按鈕。

雖然可正常運作,但缺乏樣式。小工具會公開參數,讓您自訂登入畫面的外觀。舉例來說,您可以新增公司的標誌。

自訂登入畫面

headerBuilder

透過 SignInScreen.headerBuilder 引數,您可以在登入表單上方新增任何想要的小工具。這個小工具只會顯示在行動裝置等窄螢幕上。在寬螢幕上,您可以使用 SignInScreen.sideBuilder,這會在本程式碼研究室的後續部分討論。

使用下列程式碼更新 auth_gate.dart 檔案:

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

 @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'),
               ),
             );
           },
         );
       }

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

headerBuilder 引數需要 HeaderBuilder 類型的函式,而這個函式是在 FlutterFire UI 套件中定義。

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

由於這是回呼,因此會公開可用的值,例如 BuildContextBoxConstraints,並要求您傳回小工具。您返回的任何小工具都會顯示在畫面頂端。在本例中,新程式碼會在畫面頂端加入圖片。您的應用程式現在應如下所示。

73d7548d91bbd2ab.png

字幕建立工具

登入畫面會提供三個額外參數,讓您自訂畫面:subtitleBuilderfooterBuildersideBuilder

subtitleBuilder 略有不同,因為回呼引數包含類型為 AuthAction 的動作。AuthAction 是列舉,可用來偵測使用者目前所在的畫面是「登入」畫面或「註冊」畫面。

更新 auth_gate.dart 中的程式碼,以便使用 subtitleBuilder。

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

 @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('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!'),
             );
           },
         );
       }

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

重新載入應用程式,如下所示:

footerBuilder 引數與 subtitleBuilder 相同。由於此方法是用於文字而非圖片,因此不會顯示 BoxConstraintsshrinkOffset。(不過,您可以新增任何想要的小工具)。

使用這段程式碼,在登入畫面中加入頁尾。

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

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

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

側邊建構工具

SignInScreen.sidebuilder 引數接受回呼,這次回呼的引數為 BuildContextdouble shrinkOffset。sideBuilder 傳回的小工具會顯示在登入表單左側,且僅限於寬螢幕。也就是說,小工具只會顯示在電腦版和網頁應用程式上。

FlutterFire UI 會在內部使用中斷點,判斷是否應在大螢幕 (例如行動裝置) 上顯示標頭內容,或是側邊內容 (在寬螢幕、電腦或網頁上) 是否應顯示。具體來說,如果螢幕寬度超過 800 像素,系統會顯示側邊建構工具內容,但不會顯示頁首內容。如果螢幕寬度低於 800 像素,則相反情況。

更新 auth_gate.dart 中的程式碼,新增 sideBuilder 小工具。

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

 @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('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 Web 或 MacOS)。

8dc60b4e5d7dd2d0.png

新增使用者

此時,這個畫面的所有程式碼都已完成。不過,您必須先建立「使用者」,才能登入。您可以透過「註冊」畫面完成這項操作,也可以在 Firebase 控制台中建立使用者。

如何使用控制台:

  1. 前往 Firebase 控制台的「使用者」表格。
  2. 按這裡閱讀相關說明文章
  3. 選取「flutterfire-ui-codelab」(或您使用的其他專案名稱)。就會看到這個表格:

f038fd9a58ed60d9.png

  1. 按一下「新增使用者」按鈕。

2d78390d4c5dbbfa.png

  1. 輸入新使用者的電子郵件地址和密碼。這可以是假的電子郵件地址和密碼,如我在下方圖片中所示。這個方法雖然有效,但若您使用假的電子郵件地址,「忘記密碼」功能將無法運作。

62ba0feb33d54add.png

  1. 按一下「新增使用者」

32b236b3ef94d4c7.png

您現在可以返回 Flutter 應用程式,並透過登入頁面登入使用者。應用程式應如下所示:

dd43d260537f3b1a.png

6. 設定檔畫面

FlutterFire UI 也提供 ProfileScreen 小工具,該工具同樣以幾行程式碼提供許多功能。

新增 ProfileScreen 小工具

前往文字編輯器中的 home.dart 檔案。使用下列程式碼更新:

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: [
            Image.asset('dash.png'),
            Text(
              'Welcome!',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

新筆記代碼是傳遞至 IconButton.isPressed method. 的回呼。當您按下 IconButton 時,應用程式會建立新的匿名路徑並前往該路徑。該路徑會顯示 ProfileScreen 小工具,該小工具會從 MaterialPageRoute.builder 回呼傳回。

重新載入應用程式,然後將右上角的圖示推送到應用程式列中,系統隨即會顯示如下所示的頁面:

36487fc4ab4f26a7.png

這是 FlutterFire UI 頁面提供的標準 UI。所有按鈕和文字欄位都已連結至 Firebase 驗證,並可立即使用。舉例來說,您可以在「名稱」文字欄位中輸入名稱,FlutterFire UI 會呼叫 FirebaseAuth.instance.currentUser?.updateDisplayName 方法,這會將該名稱儲存在 Firebase 中。

登出

目前,如果您按下「登出」按鈕,應用程式將不會變更。系統會將您登出,但不會將您導向 AuthGate 小工具。如要實作,請使用 ProfileScreen.actions 參數。

首先,請更新 home.dart 中的程式碼。

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: [
            Image.asset('dash.png'),
            Text(
              'Welcome!',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            const SignOutButton(),
          ],
        ),
      ),
    );
  }
}

現在,當您建立 ProfileScreen 的執行個體時,也會將動作清單傳遞至 ProfileScreen.actions 引數。這些動作的類型為 FlutterFireUiActionFlutterFireUiAction 的子類型有許多不同類別,一般來說,您可以使用這些類別通知應用程式對不同的驗證狀態變更做出回應。SignedOutAction 會呼叫回呼函式,當 Firebase 驗證狀態變更為 currentUser 為空值時,系統會呼叫該函式。

藉由新增會在 SignedOutAction 觸發時呼叫 Navigator.of(context).pop() 的回呼,應用程式會前往上一頁。在這個範例應用程式中,只有一個永久路徑,如果沒有使用者登入,就會顯示登入頁面,如果有使用者登入,則會顯示首頁。因為當使用者登出時,應用程式會顯示「登入」頁面。

自訂個人資料頁面

與「Sign in」頁面類似,您也可以自訂個人資料頁面。首先,使用者在個人資料頁面時,目前的網頁無法導回首頁。如要修正這個問題,請將 ProfileScreen 小工具設為 AppBar。

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: [
           Image.asset('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」小工具會將您傳遞的子項置於「Sign out」按鈕上方。

更新 home.dart 中的程式碼,以便在這裡顯示公司標誌,類似於登入畫面。

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: [
            Image.asset('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
flutter pub add firebase_ui_oauth_google

啟用 Google 登入提供者

接著,請在 Firebase 控制台中啟用 Google 供應器:

  1. 前往控制台的「驗證登入供應商」畫面。
  2. 按一下「新增供應商」。8286fb28be94bf30.png
  3. 選取「Google」。c4e28e6f4974be7f.png
  4. 切換標示為「啟用」的外接切換裝置,然後按下「儲存」。e74ff86990763826.png
  5. 如果出現包含下載設定檔資訊的強制回應視窗,請按一下「完成」。
  6. 確認已新增 Google 登入服務供應器。5329ce0543c90d95.png

新增 Google 登入按鈕

啟用 Google 登入功能後,請新增必要的小工具,以便在登入頁面顯示風格化的 Google 登入按鈕。請前往 auth_gate.dart 檔案,並將程式碼更新為以下程式碼:

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'; // new
import 'package:flutter/material.dart';

import 'home.dart';

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

 @override
 Widget build(BuildContext context) {
   return StreamBuilder<User?>(
     stream: FirebaseAuth.instance.authStateChanges(),
     builder: (context, snapshot) {
       if (!snapshot.hasData) {
         return SignInScreen(
           providers: [
             EmailAuthProvider(),
             GoogleProvider(clientId: "YOUR_WEBCLIENT_ID"),  // new
           ],
           headerBuilder: (context, constraints, shrinkOffset) {
             return Padding(
               padding: const EdgeInsets.all(20),
               child: AspectRatio(
                 aspectRatio: 1,
                 child: Image.asset('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();
     },
   );
 }
}

這裡唯一的新程式碼,就是將 GoogleProvider(clientId: "YOUR_WEBCLIENT_ID") 新增至 SignInScreen 小工具設定。

新增這項資訊後,請重新載入應用程式,您就會看到 Google 登入按鈕。

aca71a46a011bfb5.png

設定登入按鈕

這個按鈕需要額外設定才能運作。如果您使用 Flutter Web 進行開發,這就是唯一需要新增的步驟,即可讓這項功能運作。其他平台需要額外的步驟,我們稍後會說明。

  1. 前往 Firebase 控制台的「驗證供應商」頁面。
  2. 按一下 Google 供應商。9b3a325c5eca6e49.png
  3. 按一下「Web SDK 設定」展開面板。
  4. 複製「網路用戶端 ID」中的值 711a79f0d931c60f.png
  5. 返回文字編輯器,並將這個 ID 傳遞至名為 clientId 的參數,更新檔案 auth_gate.dart 中的 GoogleProvider 例項。
GoogleProvider(
   clientId: "YOUR_WEBCLIENT_ID"
)

輸入網頁用戶端 ID 後,請重新載入應用程式。按下「使用 Google 帳戶登入」按鈕後,系統會顯示新視窗 (如果您使用網頁),引導您完成 Google 登入流程。一開始會如下所示:

14e73e3c9de704bb.png

設定 iOS

為了讓這項功能在 iOS 上運作,您必須進行額外的設定程序。

  1. 前往 Firebase 主控台的「專案設定」畫面。系統會顯示資訊卡,列出您的 Firebase 應用程式,如下所示:fefa674acbf213cc.png
  2. 按一下 iOS。請注意,您的應用程式名稱會與我的不同。如果您使用 flutter-codelabs/firebase-auth-flutterfire-ui/start 專案搭配本程式碼研究室,在 mine 顯示「complete」(完成) 部分。
  3. 按一下「GoogleServices-Info.plist」按鈕,下載所需的設定檔。f89b3192871dfbe3.png
  4. 將下載的檔案拖曳至 目錄。/ios/Runner
  5. 在專案根目錄中執行下列終端機指令,即可開啟 Xcode:open ios/Runner.xcworkspace
  6. 在「Runner」目錄上按一下滑鼠右鍵,然後選取「Add Files to Runner」。858986063a4c5201.png
  7. 從檔案管理員選取「GoogleService-Info.plist」。
  8. 返回文字編輯器 (非 Xcode),將下列 CFBundleURLTypes 屬性加入 [my_project]/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 -->
  1. 您必須將在網頁設定中新增的 GoogleProvider.clientId 替換為與 Firebase iOS 用戶端 ID 相關聯的用戶端 ID。首先,您可以在 firebase_options.dart 檔案中找到這個 ID,做為 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',
);
  1. 將這個值貼到 AuthGate 小工具的 GoogleProvider.clientId 引數中。
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';
import 'package:flutter/material.dart';

import 'home.dart';


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

 @override
 Widget build(BuildContext context) {
   return StreamBuilder<User?>(
     stream: FirebaseAuth.instance.authStateChanges(),
     builder: (context, snapshot) {
       if (!snapshot.hasData) {
         return SignInScreen(
           providers: [
             EmailAuthProvider(),
             GoogleProvider(clientId: "YOUR IOS CLIENT ID"),  // replace String
           ],
           headerBuilder: (context, constraints, shrinkOffset) {
             return Padding(
               padding: const EdgeInsets.all(20),
               child: AspectRatio(
                 aspectRatio: 1,
                 child: Image.asset('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 應用程式已在 iOS 中執行,則必須先完全關閉應用程式,然後重新執行應用程式。在其他情況下,請在 iOS 執行應用程式。

8. 恭喜!

您已完成 Flutter 程式碼研究室適用的 Firebase 驗證 UI。您可以在 GitHub 的「complete」目錄中找到本程式碼研究室的完成版程式碼:Flutter 程式碼研究室

涵蓋內容

  • 設定 Flutter 應用程式來使用 Firebase
  • 在 Firebase 控制台中設定 Firebase 專案
  • FlutterFire CLI
  • Firebase CLI
  • 使用 Firebase 驗證
  • 使用 FlutterFire UI 在 Flutter 應用程式中輕鬆處理 Firebase 驗證

後續步驟

瞭解詳情

Sparky 要和你一起慶祝!

2a0ad195769368b1.gif