使用 FirebaseUI 向 Flutter 应用添加用户身份验证流程

1. 准备工作

在此 Codelab 中,您将学习如何使用 FlutterFire 界面软件包将 Firebase Authentication 添加到 Flutter 应用。借助此软件包,您将向 Flutter 应用添加电子邮件/密码身份验证和 Google 登录身份验证。您还将学习如何设置 Firebase 项目,以及如何使用 FlutterFire CLI 在 Flutter 应用中初始化 Firebase。

前提条件

此 Codelab 假定您具有一些 Flutter 经验。如果没有,您可能需要先了解基础知识。以下链接非常有用:

您还应具备一些 Firebase 使用经验,但即使您从未将 Firebase 添加到 Flutter 项目,也无妨。如果您不熟悉 Firebase 控制台,或者完全不了解 Firebase,请先参阅以下链接:

您将创建的内容

此 Codelab 将指导您使用 Firebase Authentication 为 Flutter 应用构建身份验证流程。该应用将包含登录界面、“注册”界面、密码恢复界面和用户个人资料界面。

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

学习内容

此 Codelab 涵盖以下内容:

  • 将 Firebase 添加到 Flutter 应用
  • Firebase 控制台设置
  • 使用 Firebase CLI 将 Firebase 添加到您的应用
  • 使用 FlutterFire CLI 在 Dart 中生成 Firebase 配置
  • 将 Firebase Authentication 添加到您的 Flutter 应用
  • 在控制台中设置 Firebase Authentication
  • 使用 firebase_ui_auth 软件包添加电子邮件地址和密码登录
  • 使用 firebase_ui_auth 软件包添加用户注册
  • 添加“忘记密码?”页面
  • 使用 firebase_ui_auth 添加 Google 登录
  • 配置应用以支持多个登录提供方。
  • 使用 firebase_ui_auth 软件包向应用添加用户个人资料页面

本 Codelab 专门介绍了如何使用 firebase_ui_auth 软件包添加强大的身份验证系统。您将看到,整个应用(包括上述所有功能)只需大约 100 行代码即可实现。

您需要满足的条件

  • 熟悉 Flutter 并已安装该 SDK
  • 文本编辑器(Flutter 支持 JetBrains IDE、Android Studio 和 VS Code)
  • Google Chrome 浏览器或您用于 Flutter 的其他首选开发目标平台。(此 Codelab 中的某些终端命令会假定您是在 Chrome 上运行应用)

2. 创建和设置 Firebase 项目

您需要完成的第一个任务是在 Firebase 的 Web 控制台中创建一个 Firebase 项目。

创建 Firebase 项目

  1. 登录 Firebase
  2. 在 Firebase 控制台中,点击添加项目(或创建项目),然后输入 Firebase 项目的名称(例如“FlutterFire-UI-Codelab”)。

df42a5e3d9584b48.png

  1. 点击各个项目创建选项。如果系统提示,请接受 Firebase 条款。跳过设置 Google Analytics 的步骤,因为您不会对此应用使用 Google Analytics。

d1fcec48bf251eaa.png

如需详细了解 Firebase 项目,请参阅了解 Firebase 项目

为 Firebase Authentication 启用电子邮件登录

您要构建的应用使用 Firebase Authentication 让用户能够登录您的应用,还允许新用户通过 Flutter 应用进行注册。

Firebase Authentication 需要使用 Firebase 控制台启用,并且启用后需要进行特殊配置。

如需允许用户登录 Web 应用,您将先使用电子邮件地址/密码登录方法。稍后,您将添加 Google 登录方法。

  1. 在 Firebase 控制台中,展开左侧面板中的 Build 菜单。
  2. 点击 Authentication,然后依次点击 Get Started 按钮和 Sign-in method 标签页(或点击此处直接进入 Sign-in method 标签页)。
  3. 点击登录提供程序列表中的电子邮件地址/密码,将启用开关切换到开启位置,然后点击保存58e3e3e23c2f16a4.png

3. 设置 Flutter 应用

在开始之前,您需要下载起始代码并安装 Firebase CLI。

获取起始代码

从命令行克隆 GitHub 代码库

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

或者,如果您已安装 GitHub 的 CLI 工具,请执行以下操作:

gh repo clone flutter/codelabs flutter-codelabs

应将示例代码克隆到您机器上的 flutter-codelabs 目录中,该目录包含一系列 Codelab 的代码。此 Codelab 的代码位于子目录 flutter-codelabs/firebase-auth-flutterfire-ui 中。

目录 flutter-codelabs/firebase-auth-flutterfire-ui 包含两个 Flutter 项目。将其中一个属性命名为 complete,另一个命名为 startstart 目录包含一个未完成的项目,您将在这里花费最多的时间。

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

如果您想跳转到后面的内容,或者查看某个内容完成后应有的样子,请查看名为“complete”的目录以进行交叉引用。

如果您想跟着本 Codelab 操作并自行添加代码,则应先从 flutter-codelabs/firebase-auth-flutterfire-ui/start 中的 Flutter 应用开始,并在整个 Codelab 过程中向该项目添加代码。打开该目录,或将其导入首选 IDE 中。

安装 Firebase CLI

Firebase CLI 提供了用于管理 Firebase 项目的工具。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. 选择要使用的平台。此 Codelab 中介绍了针对 Web、iOS 和 Android 配置 Firebase Authentication for Flutter 的步骤,但您可以设置 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 项目中适用于特定平台的特定 build。例如,名为 FlutterFire-ui-codelab 的 Firebase 项目包含多个应用:一个适用于 Android,一个适用于 iOS,一个适用于 macOS,一个适用于 Web。

方法 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
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 框架完全启动之前不要开始运行应用 widget 代码。Firebase 使用原生平台渠道,而这些渠道需要框架处于运行状态。
  2. Firebase.initializeApp 会在您的 Flutter 应用和 Firebase 项目之间建立连接。DefaultFirebaseOptions.currentPlatform 是从我们生成的 firebase_options.dart 文件导入的。此静态值会检测您所运行的平台,并传入相应的 Firebase 密钥。

4. 添加了初始 Firebase 界面 Auth 页面

Firebase Auth 的界面提供了用于表示应用中整个屏幕的 widget。这些界面用于处理应用中的不同身份验证流程,例如登录、注册、忘记密码、用户个人资料等。首先,请向应用添加一个着陆页,该着陆页将充当主应用的身份验证守卫。

Material 或 Cupertino 应用

FlutterFire 界面要求您的应用封装在 MaterialApp 或 CupertinoApp 中。界面会根据您的选择自动反映 Material 或 Cupertino Widget 的差异。对于此 Codelab,请使用 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 在其 build 方法中构建了 AuthGate 微件。(这是一个自定义 widget,并非由 FlutterFire 界面提供。)

该微件需要更新,以包含 authStateChanges 数据流。

authStateChanges API 会返回一个 Stream,其中包含当前用户(如果已登录)或 null(如果未登录)。如需在应用中订阅此状态,您可以使用 Flutter 的 StreamBuilder widget 并将数据流传递给它。

StreamBuilder 是一个 widget,它会根据您传递的 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 widget。目前,该屏幕不会执行任何操作。系统会在下一步中更新此信息。
  • 否则,它会返回 HomeScreen,这是应用的主要部分,只有经过身份验证的用户才能访问。

SignInScreen 是来自 FlutterFire 界面软件包的 widget。这是本 Codelab 后续步骤的重点。此时,运行应用后,您应该会看到空白的登录界面。

5. 登录屏幕

FlutterFire 界面提供的 SignInScreen 微件添加了以下功能:

  • 允许用户登录
  • 如果用户忘记了密码,可以点按“忘记密码?”,系统会将其转到用于重置密码的表单
  • 如果用户尚未注册,可以点按“注册”,系统会将其转到另一个表单,以便其进行注册。

同样,这只需要几行代码。回想一下 AuthGate widget 中的代码:

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 widget 及其 providers 参数是实现上述所有功能所需的唯一代码。现在,您应该会看到一个登录界面,其中包含“电子邮件地址”和“密码”文本输入框,以及一个“登录”按钮。

虽然功能正常,但缺少样式。该 widget 会公开参数,以便自定义登录屏幕的外观。例如,您可能需要添加公司徽标。

自定义登录屏幕

headerBuilder

您可以使用 SignInScreen.headerBuilder 参数在登录表单上方添加任何所需的 widget。此微件仅在窄屏设备(例如移动设备)上显示。在宽屏设备上,您可以使用 SignInScreen.sideBuilder,此 Codelab 稍后会对其进行介绍。

使用以下代码更新 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 界面软件包中定义。

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

由于它是一个回调,因此会公开您可以使用的值(例如 BuildContextBoxConstraints),并且要求您返回一个 widget。您返回的任何微件都会显示在屏幕顶部。在此示例中,新代码会将图片添加到屏幕顶部。您的应用现在应如下所示。

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,因为它适用于文本而非图片。(不过,您可以添加任何所需的 widget)。

使用以下代码向登录屏幕添加页脚。

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

Side Builder

SignInScreen.sidebuilder 参数接受回调,而这次该回调的参数为 BuildContextdouble shrinkOffset。sideBuilder 返回的 widget 将显示在登录表单的左侧,并且仅在宽屏设备上显示。这实际上意味着,该微件将仅在桌面应用和 Web 应用中显示。

在内部,FlutterFire 界面使用断点来确定应显示标题内容(在高屏幕上,例如移动设备)还是侧边内容(在宽屏幕上,例如桌面设备或网站)。具体而言,如果屏幕宽度超过 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 界面还提供了 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(),
          ],
        ),
      ),
    );
  }
}

note 的新代码是传递给 IconButton.isPressed method. 的回调。当用户按下该 IconButton 时,您的应用会创建一条新的匿名路线并导航到该路线。该路由将显示 ProfileScreen 微件,该微件会从 MaterialPageRoute.builder 回调返回。

重载应用,然后点按右上角(在应用栏中)的图标,系统会显示如下页面:

36487fc4ab4f26a7.png

这是 FlutterFire 界面页面提供的标准界面。所有按钮和文本字段都已连接到 Firebase Auth,并可开箱即用。例如,您可以在“名称”文本字段中输入名称,FlutterFire 界面将调用 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 有许多不同的子类,通常,您可以使用这些子类指示应用对不同的身份验证状态变化做出响应。当 Firebase Auth 状态更改为 currentUser 为 null 时,SignedOutAction 会调用您提供的回调函数。

通过添加一个在 SignedOutAction 触发时调用 Navigator.of(context).pop() 的回调,应用将转到上一页。在此示例应用中,只有一条永久路线,如果没有用户登录,则显示登录页面;如果有用户登录,则显示首页。由于这种情况发生在用户退出账号时,因此应用会显示登录页面。

自定义“个人资料”页面

与登录页面类似,个人资料页面也可自定义。首先,在用户进入个人资料页面后,我们当前的页面无法返回到首页。为 ProfileScreen widget 提供 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 微件,因此可以像您构建的任何其他 AppBar 一样被视为,并传递给 Scaffold。在此示例中,系统会保留自动添加“返回”按钮的默认功能,并且屏幕现在具有标题。

将儿童添加到“个人资料”页面

ProfileScreen 微件还有一个名为 children 的可选实参。此参数接受一个微件列表,这些微件将垂直放置在内部已用于构建 ProfileScreen 的 Column 微件中。ProfileScreen build 方法中的这个 Column 微件会将您传递给它的子项放置在“退出账号”按钮上方。

更新 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 界面还提供用于通过第三方提供程序(例如 Google、Twitter、Facebook、Apple 和 GitHub)进行身份验证的 widget 和功能。

如需与 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 登录按钮所需的 widget 添加到登录页面。前往 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 客户端 ID”的值 711a79f0d931c60f.png
  5. 返回文本编辑器,将此 ID 传递给名为 clientId 的参数,以更新文件 auth_gate.dart 中的 GoogleProvider 实例。
GoogleProvider(
   clientId: "YOUR_WEBCLIENT_ID"
)

输入 Web 客户端 ID 后,请重新加载您的应用。当您按下“使用 Google 账号登录”按钮时,系统会显示一个新窗口(如果您使用的是 Web 版),引导您完成 Google 登录流程。最初,它看起来如下所示:

14e73e3c9de704bb.png

配置 iOS

为了让此功能在 iOS 上正常运行,需要执行额外的配置流程。

  1. 前往 Firebase 控制台中的“项目设置”界面。系统会显示一个卡片,其中列出了您的 Firebase 应用,如下所示:fefa674acbf213cc.png
  2. 点击“iOS”。请注意,您的应用名称与我的应用名称不同。如果您使用 flutter-codelabs/firebase-auth-flutterfire-ui/start 项目跟随本 Codelab 中的步骤,则我的“完成”将显示为“开始”。
  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),将以下 CFBundle网址Types 属性添加到 [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. 您需要将在 Web 设置中添加的 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 Auth 界面”Codelab。您可以在 GitHub 上的“complete”目录中找到此 Codelab 的完整代码:Flutter Codelab

所学内容

  • 设置 Flutter 应用以使用 Firebase
  • 在 Firebase 控制台中设置 Firebase 项目
  • FlutterFire CLI
  • Firebase CLI
  • 使用 Firebase Authentication
  • 使用 FlutterFire UI 轻松在 Flutter 应用中处理 Firebase Authentication

后续步骤

了解详情

Sparky 与您一起庆祝!

2a0ad195769368b1.gif