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

1. 事前準備

在本程式碼研究室中,您將瞭解如何使用 FlutterFire UI 套件,將 Firebase 驗證功能新增至 Flutter 應用程式。透過這個套件,您將在 Flutter 應用程式中加入電子郵件和密碼驗證和 Google 登入驗證。您也會瞭解如何設定 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 套件新增使用者註冊
  • 新增「忘記密碼?」頁面
  • 新增 Google 登入功能,使用 firebase_ui_auth
  • 設定應用程式以支援多個登入服務供應器。
  • 使用 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 控制台中,按一下「Add Project」 (或「Create a project」),然後輸入 Firebase 專案名稱 (例如「FlutterFire-UI-Codelab」)。df42a5e3d9584b48.png
  3. 點選專案建立選項。當系統顯示提示時,請接受 Firebase 條款。略過設定 Google Analytics,因為這個應用程式不會用到 Analytics。d1fcec48bf251eaa.png

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

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

您正在建構的應用程式會使用 Firebase 驗證,讓使用者登入應用程式,也能讓新使用者透過 Flutter 應用程式註冊。

您必須透過 Firebase 控制台啟用 Firebase 驗證,並在啟用後進行特殊設定。

為了讓使用者登入網頁應用程式,您必須先使用「電子郵件/密碼」登入方式。稍後您將新增 Google 登入方法。

  1. 在 Firebase 控制台中,展開左側面板中的「Build」選單。
  2. 依序點選「Authentication」和「Get Started」按鈕,然後點選「Sign-in method」分頁標籤 (或直接前往「Sign-in method」分頁標籤)。
  3. 在「Sign-in providers」清單中點選「電子郵件/密碼」,將「Enable」切換到開啟位置,然後點選「Save」

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 專案的工具。這個 CLI 是 FlutterFire CLI 的必要條件,您稍後會安裝這個 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 產生所需的 Dart 程式碼,在 Flutter 應用程式中使用 Firebase。

flutterfire configure

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

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

  1. 選取要使用的專案。在這種情況下,請使用 flutterfire-ui-codelab1359cdeb83204baa.png
  2. 選取要使用的平台。本程式碼研究室中提供設定 Flutter 適用於網頁、iOS 和 Android 的 Firebase 驗證機制的步驟,但您可以設定 Firebase 專案,以便使用所有選項。301c9534f594f472.png
  3. 這張螢幕截圖顯示程序結束時的輸出內容。如果您熟悉 Firebase,就會發現您不必在主控台中建立平台應用程式 (例如 Android 應用程式),FlutterFire CLI 會代為完成。12199a85ade30459.png

完成後,請在文字編輯器中查看 Flutter 應用程式。FlutterFire CLI 已修改名為 firebase_options.dart 的檔案。這個檔案包含名為 FirebaseOptions 的類別,其中包含靜態變數,可保留各平台所需的 Firebase 設定。如果您在執行 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 設定值。

將 Firebase 套件新增至 Flutter 應用程式

最後一個設定步驟,就是將相關 Firebase 套件新增至 Flutter 專案。firebase_options.dart 檔案應會出現錯誤,因為它依賴尚未新增的 Firebase 套件。在終端機中,請確認您位於 flutter-codelabs/firebase-emulator-suite/start 的 Flutter 專案根目錄中。接著,請執行下列三個指令:

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

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

  1. WidgetsFlutterBinding.ensureInitialized() 會告訴 Flutter 在 Flutter 框架完全啟動前,不要開始執行應用程式小工具程式碼。Firebase 會使用原生平台管道,因此需要執行框架。
  2. Firebase.initializeApp 會在 Flutter 應用程式和 Firebase 專案之間建立連線。DefaultFirebaseOptions.currentPlatform 是從產生的 firebase_options.dart 檔案匯入。這個靜態值會偵測您執行的平台,並傳入對應的 Firebase 鍵。

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

Firebase 驗證工具提供小工具,可代表應用程式中的整個畫面。這些畫面會處理應用程式中的不同驗證流程,例如登入、註冊、忘記密碼、使用者個人資料等。首先,請在應用程式中新增登陸頁面,做為主要應用程式的驗證守衛。

Material 或 Cupertino 應用程式

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

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 驗證外掛程式,監聽 FirebaseAuthauthStateChanges

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

您需要更新該小工具,才能納入 authStateChanges 串流。

authStateChanges API 會傳回 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 套件的小工具。本程式碼研究室的後續步驟將著重於這項作業。此時執行應用程式時,您應該會看到空白的登入畫面。

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 引數,在登入表單上方新增任何小工具。這個小工具只會顯示在行動裝置等窄螢幕上。在寬螢幕上,您可以使用 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

字幕建立工具

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

subtitleBuilder 稍有不同,因為回呼引數包含 AuthAction 類型的動作。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();
      },
    );
  }
}

側邊建構工具

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

在內部,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 時,應用程式會建立新的匿名路徑並前往該路徑。該路徑會顯示 ProfileScreen 小工具,該小工具會從 MaterialPageRoute.builder 回呼傳回。

重新載入應用程式,然後按下右上方 (應用程式列) 的圖示,畫面就會顯示如下所示的頁面:

36487fc4ab4f26a7.png

這是 FlutterFire UI 頁面提供的標準 UI。所有按鈕和文字欄位都已連結至 Firebase 驗證,並可立即使用。舉例來說,您可以在「Name」文字欄位中輸入名稱,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 引數。這些動作的類型為 FlutterFireUiActionFlutterFireUiAction 的子類別有很多,您通常會使用這些類別,讓應用程式對不同的驗證狀態變更做出反應。SignedOutAction 會呼叫回呼函式,您在 Firebase 驗證狀態變更為 currentUser 為空值時,會將此函式提供給該函式。

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

自訂個人資料頁面

與登入畫面類似,您也可以自訂個人資料頁面。首先,使用者在個人資料頁面時,目前的網頁無法返回首頁。請為 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。這個引數會接受小工具清單,這些小工具會垂直放置在 Column 小工具中,而該小工具已用於內部建構 ProfileScreenProfileScreen 建構方法中的這個 Column 小工具會將您傳遞的子項放在「Sign out」按鈕上方。

更新 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. 按一下「新增供應商」。8286fb28be94bf30.png
  3. 選取「Google」。c4e28e6f4974be7f.png
  4. 切換「Enable」(啟用) 按鈕,然後按下「Save」(儲存)。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();
      },
    );
  }
}

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

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

aca71a46a011bfb5.png

設定登入按鈕

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

  1. 前往 Firebase 主控台中的「驗證服務供應器」頁面。
  2. 按一下 Google 供應商。9b3a325c5eca6e49.png
  3. 按一下「Web SDK 設定」展開面板。
  4. 複製「Web client 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 專案跟著本程式碼研究室的說明操作,螢幕截圖中顯示的「complete」會變成「start」。
  3. 按一下標示為 GoogleServices-Info.plist 的按鈕,下載所需的設定檔。f89b3192871dfbe3.png
  4. 將下載的檔案拖曳至 Flutter 專案中的 /ios/Runner 目錄。
  5. 在專案根目錄中執行下列終端機指令,即可開啟 Xcode:open ios/Runner.xcworkspace
  6. 在 Runner 目錄上按一下滑鼠右鍵,然後選取「Add Files to "Runner"」(將檔案新增至「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。首先,您可以在 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',
    );
    
  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. 恭喜!

您已完成 Flutter 程式碼研究室的 Firebase 驗證 UI 課程。您可以在 GitHub 的 firebase-auth-flutterfire-ui/complete 目錄中找到本程式碼研究室的完整程式碼

涵蓋內容

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

後續步驟

瞭解詳情

Sparky 在此為你獻上祝福!

2a0ad195769368b1.gif