使用 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 eab9509a41074930 da49189a5838e0bb 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 的网络控制台中创建一个 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 控制台中,展开左侧面板中的构建菜单。
  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。

您可以通过多种方式安装 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 是基于 Firebase CLI 构建的工具,可帮助您在 Flutter 应用的所有受支持平台上简化 Firebase 的安装过程。

首先,安装 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 版 Flutter 配置 Firebase Authentication,但您可以将 Firebase 项目设置为使用所有选项。301c9534f594f472.png
  3. 此屏幕截图显示了该过程结束时的输出。如果您熟悉 Firebase,就会发现无需在控制台中创建平台应用(例如 Android 应用),而 FlutterFire CLI 已为您完成创建。12199a85ade30459

完成后,请在文本编辑器中查看 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 界面身份验证页面

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

Material 或 Cupertino 应用

FlutterFire 界面要求您的应用封装在 MaterialApp 或 CupertinoApp 中。根据您的选择,界面会自动反映 Material 或 Cupertino 微件的差异。对于此 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 widget 添加了以下功能:

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

同样,这只需要几行代码。回想一下 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 参数是实现上述所有功能所需的唯一代码。现在,您应该会看到包含“email”和“password”文本输入的登录屏幕以及“Sign In”按钮。

尽管它能正常运行,但缺少样式。该 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,因为它适用于文本而非图片。(但您可以根据需要添加任何微件。)

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

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

  1. 点击“添加用户”按钮。

2d78390d4c5dbbfa.png

  1. 输入新用户的电子邮件地址和密码。这可能是虚假的电子邮件地址和密码,如下图所示。这样可以,但如果您使用的是虚假电子邮件地址,“忘记密码”功能将无法使用。

62ba0feb33d54add

  1. 点击“添加用户”

32b236b3ef94d4c7.png

现在,您可以返回 Flutter 应用,并通过登录页面登录用户。您的应用应如下所示:

dd43d260537f3b1a.png

6. 个人资料屏幕

FlutterFire 界面还提供了一个 ProfileScreen widget,它同样只需几行代码即可为您提供很多功能。

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

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

36487fc4ab4f26a7

这是 FlutterFire 界面页面提供的标准界面。所有按钮和文本字段都已连接到 Firebase Auth,并可开箱即用。例如,您可以在“Name”文本字段输入一个名称,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 实参。这些操作的类型为 FlutterFireUiAction。有许多不同的类是 FlutterFireUiAction 的子类型,一般来说,您可以使用这些类来指示应用响应不同的身份验证状态变化。SignedOutAction 会调用一个回调函数,您可以在 Firebase 身份验证状态变为 currentUser 为 null 时向其提供该函数。

添加在 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 widget 还有一个名为 child 的可选参数。此参数接受 widget 列表,这些 widget 将垂直放置在已在内部用于构建 ProfileScreen 的 Column widget。ProfileScreen 构建方法中的这个 Column widget 会将您传递的子微件放在“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 Auth 登录

FlutterFire 界面还提供微件和功能,用于向第三方提供商(例如 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
  3. 选择“Google”。c4e28e6f4974be7f
  4. 切换标有“启用”的开关,然后按“保存”。e74ff86990763826
  5. 如果系统显示包含下载配置文件相关信息的模态窗口,请点击“完成”。
  6. 确认已添加 Google 登录提供方。5329ce0543c90d95

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

此处的唯一新代码是在 SignInScreen widget 配置中添加 GoogleProvider(clientId: "YOUR_WEBCLIENT_ID")

添加完毕后,请重新加载应用,您会看到 Google 登录按钮。

aca71a46a011bfb5

配置登录按钮

如果不进行其他配置,该按钮将不起作用。如果您使用 Flutter Web 进行开发,则只需执行此步骤即可正常运行。其他平台需要执行额外的步骤,我们稍后会讨论这些步骤。

  1. 前往 Firebase 控制台中的“身份验证提供方”页面。
  2. 点击相应的 Google 提供方。9b3a325c5eca6e49.png
  3. 点击“Web SDK 配置”展开面板。
  4. 复制“Web 客户端 ID”的值 711a79f0d931c60f
  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。请注意,您的应用名称与我的应用名称不同。如果您按照此 Codelab 的进度使用 flutter-codelabs/firebase-auth-flutterfire-ui/start 项目,我的代码为“complete”(完成),则代码为“start”。
  3. 点击“GoogleServices-Info.plist”按钮,下载所需的配置文件。F89b3192871dfbe3.png
  4. 将下载的文件拖放到名为 的目录中。/ios/Runner
  5. 从项目的根目录运行以下终端命令,以打开 Xcode:open ios/Runner.xcworkspace
  6. 右键点击 Runner 目录,然后选择“Add Files to 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. 您需要将在网站设置中添加的 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 widget 的 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 身份验证界面”Codelab。您可以在 GitHub 上的“complete”目录中找到此 Codelab 完成后的代码:Flutter Codelab

所学内容

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

后续步骤

了解详情

Sparky 在这里与您一起庆祝!

2a0ad195769368b1.gif