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

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

程式碼研究室簡介

subject上次更新時間:1月 6, 2025
account_circle作者:Eric Windmill

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 控制台中,按一下「新增專案」 (或「建立專案」),然後輸入 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 專案的工具。這個 CLI 是 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 是一項工具,可在 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-codelab 1359cdeb83204baa.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 的靜態值。

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 設定值。

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

最後一個設定步驟,就是將相關 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 驗證工具提供小工具,可代表應用程式中的整個畫面。這些畫面會處理應用程式中的不同驗證流程,例如登入、註冊、忘記密碼、使用者個人資料等。首先,請在應用程式中新增登陸頁面,做為主要應用程式的驗證守衛。

Material 或 Cupertino 應用程式

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

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 中最新的資料快照來建構自身。當 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 方法的回呼。按下 IconButton 時,應用程式會建立新的匿名路徑並前往該路徑。該路徑會顯示 ProfileScreen 小工具,該小工具會從 MaterialPageRoute.builder 回呼傳回。

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

36487fc4ab4f26a7.png

這是 FlutterFire UI 頁面提供的標準 UI。所有按鈕和文字欄位都已連結至 Firebase 驗證,並可立即使用。舉例來說,您可以在「Name」文字欄位中輸入名稱,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() 的回呼,應用程式就會導覽至先前的頁面。在這個範例應用程式中,只有一個永久路徑,如果沒有使用者登入,就會顯示登入頁面,如果有使用者登入,則會顯示首頁。由於這項事件發生在使用者登出時,因此應用程式會顯示「SignIn」頁面。

自訂個人資料頁面

與「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. 複製「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. 將下載的檔案拖曳至名為 的目錄。/ios/Runner 在您的 Flutter 專案中。
  5. 在專案根目錄中執行下列終端機指令,即可開啟 Xcode:open ios/Runner.xcworkspace
  6. 在 Runner 目錄上按一下滑鼠右鍵,然後選取「Add Files to "Runner"」(將檔案新增至「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