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

1. 准备工作

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

前提条件

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

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

您将创建的内容

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

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

学习内容

此 Codelab 涵盖以下内容:

  • 将 Firebase 添加到 Flutter 应用
  • Firebase 控制台设置
  • 使用 Firebase CLI 将 Firebase 添加到您的应用
  • 使用 FlutterFire CLI 在 Dart 中生成 Firebase 配置
  • 向 Flutter 应用添加 Firebase Authentication
  • 在控制台中设置 Firebase 身份验证
  • 使用 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. 使用您的 Google 账号登录 Firebase 控制台
  2. 点击相应按钮以创建新项目,然后输入项目名称(例如 FlutterFire-UI-Codelab)。
  3. 点击继续
  4. 如果看到相关提示,请查看并接受 Firebase 条款,然后点击继续
  5. (可选)在 Firebase 控制台中启用 AI 辅助功能(称为“Gemini in Firebase”)。
  6. 在此 Codelab 中,您不需要使用 Google Analytics,因此请关闭 Google Analytics 选项。
  7. 点击创建项目,等待项目完成预配,然后点击继续

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

为 Firebase Authentication 启用电子邮件登录

您正在构建的应用使用 Firebase Authentication,以便用户登录您的应用。它还允许新用户通过 Flutter 应用进行注册。

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

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

  1. 在 Firebase 控制台中,展开左侧面板中的构建菜单。
  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 目录中,该目录包含一系列 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。如需查看适用于您操作系统的所有可用选项,请访问 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. 选择要使用的平台。在此 Codelab 中,我们提供了针对 Web、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 项目中特定平台上的特定 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 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 框架完全启动之前不要开始运行应用 widget 代码。Firebase 使用原生平台渠道,这需要框架处于运行状态。
  2. Firebase.initializeApp 用于在 Flutter 应用和 Firebase 项目之间建立连接。DefaultFirebaseOptions.currentPlatform 是从我们生成的 firebase_options.dart 文件中导入的。此静态值可检测您正在运行的平台,并传入相应的 Firebase 密钥。

4. 添加初始 Firebase 界面身份验证页面

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

Material 应用或 Cupertino 应用

FlutterFire 界面要求您的应用封装在 MaterialAppCupertinoApp 中。根据您的选择,界面会自动反映 Material 或 Cupertino widget 的差异。在此 Codelab 中,您将使用 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 widget。(这是一个自定义 widget,并非由 FlutterFire 界面提供。)

该 widget 需要更新,以包含 authStateChanges 流。

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

StreamBuilder 是一个根据您传递给它的 Stream 中的最新数据快照自行构建的 widget。当 Stream 发出新的快照时,它会自动重建。

更新了 auth_gate.dart 中的代码。

lib/auth_gate.dart

import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider; // Add this import
import 'package:firebase_ui_auth/firebase_ui_auth.dart';                  // And this import
import 'package:flutter/material.dart';

import 'home.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key, required this.clientId});

  final String clientId;

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

        return const HomeScreen();
      },
    );                                                                 // To here.
  }
}
  • StreamBuilder.stream 正在传递 FirebaseAuth.instance.authStateChanged(即上述流),如果用户已通过身份验证,该流将返回 Firebase User 对象,否则将返回 null
  • 接下来,代码使用 snapshot.hasData 检查来自流的值是否包含 User 对象。
  • 如果没有,则返回 SignInScreen widget。目前,此屏幕不会执行任何操作,我们将在下一步中对其进行更新。
  • 否则,它会返回 HomeScreen,这是应用的主要部分,只有经过身份验证的用户才能访问。

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

5. 登录界面

FlutterFire UI 提供的 SignInScreen widget 添加了以下功能:

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

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

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

虽然功能正常,但缺少样式。该 widget 会公开一些参数,用于自定义登录界面的外观。例如,您可能想要添加公司徽标。

自定义登录界面

headerBuilder

使用 SignInScreen.headerBuilder 实参,您可以在登录表单上方添加所需的任何 widget。此 widget 仅显示在窄屏幕上,例如移动设备。在宽屏上,您可以使用 SignInScreen.sideBuilder,我们将在本 Codelab 的后面部分讨论该属性。

使用以下代码更新 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),并要求您返回 widget。无论您返回哪个 widget,都会显示在屏幕顶部。在此示例中,新代码会在屏幕顶部添加一张图片。您的应用现在应如下所示。

73d7548d91bbd2ab.png

字幕生成器

登录界面公开了三个额外的参数,可用于自定义界面:subtitleBuilderfooterBuildersideBuilder

subtitleBuilder 的不同之处在于,回调实参包含一个操作,其类型为 AuthActionAuthAction 是一个枚举,可用于检测用户所在的屏幕是“登录”屏幕还是“注册”屏幕。

更新 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,因为它是为文本而非图片设计的。当然,您可以添加任何所需的 widget。

使用此代码向登录界面添加页脚。

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 返回的 widget 将显示在登录表单的左侧,并且仅在宽屏幕上显示。实际上,这意味着该 widget 将仅在桌面设备和 Web 应用上显示。

在内部,FlutterFire 界面使用断点来确定是应显示标题内容(在手机等高屏幕上),还是应显示侧边内容(在桌面设备或网页等宽屏幕上)。具体来说,如果屏幕宽度超过 800 像素,则会显示侧边构建器内容,而不会显示标题内容。如果屏幕宽度小于 800 像素,则情况正好相反。

更新 auth_gate.dart 中的代码以添加 sideBuilder widget。

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 Web 或 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 widget,同样只需几行代码即可实现许多功能。

添加“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 时,您的应用会创建一个新的匿名路线并导航到该路线。该路由将显示从 MaterialPageRoute.builder 回调返回的 ProfileScreen widget。

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

36487fc4ab4f26a7.png

这是 FlutterFire 界面页面提供的标准界面。所有按钮和文本字段都已连接到 Firebase Auth,并且开箱即可使用。例如,您可以在“名称”文本字段中输入一个名称,然后 FlutterFire 界面会调用 FirebaseAuth.instance.currentUser?.updateDisplayName 方法,该方法会将该名称保存在 Firebase 中。

退出

目前,如果您按“退出”按钮,应用不会发生任何变化。系统会退出您的账号,但不会将您重新导航到 AuthGate widget。如需实现此功能,请使用 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 为 null 时,SignedOutAction 会调用您为其提供的回调函数。

通过添加在 SignedOutAction 触发时调用 Navigator.of(context).pop() 的回调,应用将导航到上一页。在此示例应用中,只有一个永久性路线,即在没有用户登录时显示“登录”界面,在有用户登录时显示首页。由于这种情况发生在用户退出账号时,应用将显示“登录”界面。

自定义个人资料页面

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

向个人资料界面添加了“儿童”

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

更新 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 Auth 登录

FlutterFire 界面还提供用于通过 Google、Twitter、Facebook、Apple 和 GitHub 等第三方提供商进行身份验证的 widget 和功能。

如需与 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 登录按钮所需的 widget。前往 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 widget 配置中。

添加完成后,重新加载应用,您会看到一个 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 后,重新加载应用。如果您使用的是 Web,当您按“使用 Google 账号登录”按钮时,系统会显示一个新窗口,引导您完成 Google 登录流程。最初,它看起来像这样:

14e73e3c9de704bb.png

配置 iOS

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

  1. Firebase 控制台中,前往“项目设置”界面。系统会显示一张列出您的 Firebase 应用的卡片,如下所示:fefa674acbf213cc.png
  2. 选择 iOS。请注意,您的应用名称将与屏幕截图中显示的名称不同。如果使用 flutter-codelabs/firebase-auth-flutterfire-ui/start 项目来完成此 Codelab,那么屏幕截图中显示“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. 恭喜!

您已完成 Firebase Auth 界面(适用于 Flutter)Codelab。您可以在 GitHub 上的 firebase-auth-flutterfire-ui/complete 目录中找到此 Codelab 的完整代码。

所学内容

  • 设置 Flutter 应用以使用 Firebase
  • 在 Firebase 控制台中设置 Firebase 项目
  • FlutterFire CLI
  • Firebase CLI
  • 使用 Firebase Authentication
  • 使用 FlutterFire UI 在 Flutter 应用中处理 Firebase 身份验证

后续步骤

了解详情

Sparky 来与您一起庆祝了!

2a0ad195769368b1.gif