使用 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 設定
  • 將 Firebase Authentication 新增至 Flutter 應用程式
  • 在控制台中設定 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. 使用 Google 帳戶登入 Firebase 控制台
  2. 按一下按鈕建立新專案,然後輸入專案名稱 (例如 FlutterFire-UI-Codelab)。
  3. 按一下「繼續」
  4. 如果系統提示,請詳閱並接受 Firebase 條款,然後按一下「繼續」
  5. (選用) 在 Firebase 控制台中啟用 AI 輔助功能 (稱為「Gemini in Firebase」)。
  6. 本程式碼研究室不需要 Google Analytics,因此請關閉 Google Analytics 選項。
  7. 按一下「建立專案」,等待專案佈建完成,然後按一下「繼續」

如要進一步瞭解 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」清單中點選「Email/Password」,將「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 專案的工具。您稍後會安裝 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. 選取要使用的平台。本程式碼研究室提供步驟,說明如何為網頁、iOS 和 Android 版 Flutter 設定 Firebase Authentication,但您可以設定 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 專案的根目錄 flutter-codelabs/firebase-emulator-suite/start。然後執行下列三項指令:

flutter pub add firebase_core firebase_auth firebase_ui_auth

目前只需要這些套件。

初始化 Firebase

如要使用新增的套件,請更新 main.dart 檔案中 main 函式的程式碼。DefaultFirebaseOptions.currentPlatform,

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 驗證 UI 提供的小工具代表應用程式中的整個畫面。這些畫面會處理應用程式中的不同驗證流程,例如登入、註冊、忘記密碼、使用者設定檔等。如要開始使用,請在應用程式中新增登陸頁面,做為主要應用程式的驗證防護機制。

Material 或 Cupertino 應用程式

FlutterFire UI 要求應用程式包裝在 MaterialAppCupertinoApp 中。根據您的選擇,使用者介面會自動反映 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 Auth 外掛程式監聽 FirebaseAuthauthStateChanges

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

該小工具必須更新,才能納入 authStateChanges 串流。

authStateChanges API 會傳回 Stream,其中包含目前使用者 (如果已登入),或 null (如果未登入)。如要在應用程式中訂閱這個狀態,可以使用 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.
  }
}
  • 正在傳遞 FirebaseAuth.instance.authStateChanged,上述串流會傳回 Firebase User 物件 (如果使用者已通過驗證),否則會傳回 nullStreamBuilder.stream
  • 接著,程式碼會使用 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();
      },
    );
  }
}

Side Builder

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 引數。這些動作屬於 FlutterFireUiAction 類型。FlutterFireUiAction 有許多不同的子型別,一般來說,您會使用這些子型別,告知應用程式對不同的驗證狀態變更做出反應。當 Firebase 驗證狀態變更為 currentUser 為空值時,SignedOutAction 會呼叫您提供的回呼函式。

新增在 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 的選用引數。這個引數會接受小工具清單,這些小工具會垂直放置在內部已用於建構 ProfileScreenColumn 小工具中。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. 按一下「新增供應商」。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();
      },
    );
  }
}

這裡唯一的新程式碼,就是將 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 專案完成本程式碼研究室,螢幕截圖中顯示「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