多平台 Firestore Flutter

目标

在此 Codelab 中,您将构建一个由 FlutterCloud Firestore 提供支持的多平台餐厅推荐应用。

最终应用可通过单个 Dart 代码库在 Android、iOS 和 Web 上运行。

5e7215e72fa571b.png

学习内容

  • 从 Flutter 应用读取数据并将数据写入 Cloud Firestore 中
  • 实时监听 Cloud Firestore 数据的变化
  • 使用 Firebase Authentication 和安全规则来保护 Cloud Firestore 数据
  • 编写复杂的 Cloud Firestore 查询和事务

您想通过此 Codelab 学习哪些内容?

我不熟悉这个主题,想大致了解一下。 我对这个主题有所了解,想回顾一下。 我在寻找示例代码以用于我的项目。 我在寻找有关特定内容的说明。

所需条件

如果您不熟悉 Flutter 或 Firestore,请先完成 Firebase for Flutter Codelab:

为完成此 Codelab,您需要:

  • 您选择的 IDE 或文本编辑器,例如配置了 Dart 和 Flutter 插件的 Android StudioVS Code
  • Google Chrome 浏览器,并且对 Chrome 开发者工具有一定了解。
  • 已启用 Web 支持的最新版 Flutterbeta 或更高版本)。您需要在此 Codelab 中配置 Web 支持,如需了解详情,请参阅 Flutter 的 Web 支持页面。
  • npm 工具,用于为此 Codelab 的最后几部分(部署索引保护数据安全以及部署到 Firebase Hosting)安装官方 firebase 命令行工具。
  • (可选)一台联网的 Android 设备或模拟器(如果需要针对 Android 编译应用的话)。
  • (可选)一台装有较新版 XCode 的 Mac 电脑(如果需要针对 iOS 编译应用的话)。

创建 Firebase 项目

  1. Firebase 控制台中,点击添加项目,然后将 Firebase 项目命名为 FriendlyEats。请记住您的 Firebase 项目 ID(也可以点击修改图标设置自己偏好的项目 ID)。
  2. 点击创建项目

您构建的应用可以使用 Web 上提供的多种 Firebase 服务:

  • Firebase Authentication:用于轻松识别用户
  • Cloud Firestore:用于在云端保存结构化数据,并在数据更新时即时获取通知
  • Firebase Hosting:用于托管和提供您的静态资源

接下来,您将了解如何使用 Firebase 控制台配置和启用这些服务。

启用匿名身份验证

虽然身份验证并不是此 Codelab 的重点,但您的应用中一定要有某种形式的身份验证。您将使用匿名登录,这意味着用户会静默登录,不会收到任何提示。

如需启用匿名登录,请执行以下操作:

  1. 在 Firebase 控制台中,在左侧导航栏中找到开发部分。
  2. 点击身份验证,然后点击登录方法标签页(或直接转到 Firebase 控制台)。
  3. 启用匿名登录服务提供方,然后点击保存

fee6c3ebdf904459.png

通过启用匿名登录,该应用可以在用户访问 Web 应用时让其静默登录。如需了解详情,请参阅匿名身份验证文档

启用 Cloud Firestore

该应用使用 Cloud Firestore 保存并接收餐厅信息和评分。

如需启用 Cloud Firestore,请执行以下操作:

  1. 在 Firebase 控制台的开发部分,点击数据库
  2. 在 Cloud Firestore 窗格中点击创建数据库

57e83568e05c7710.png

  1. 选择以测试模式开始选项,并在阅读有关安全规则的免责声明后点击启用

测试模式可确保您在开发过程中可以随意向数据库写入数据。此 Codelab 后面会介绍如何提升数据库的安全性。

daef1061fc25acc7.png

从命令行克隆 GitHub 代码库

git clone https://github.com/FirebaseExtended/codelab-friendlyeats-flutter.git friendlyeats-flutter

应将相应的示例代码克隆到 📁friendlyeats-flutter 目录中。从现在开始,请确保从此目录中运行命令:

cd friendlyeats-flutter

导入入门版应用

打开 📁friendlyeats-flutter 目录,或将其导入首选 IDE 中。这个目录包含此 Codelab 的起始代码,其中包含一个还没法运行的餐厅推荐应用。

您将通过此 Codelab 让它正常运行起来,因此请尽快修改该目录中的代码。

找到要处理的文件

尽管 Flutter 应用的常规入口点是其 lib/main.dart 文件,但在此 Codelab 中,您需要侧重于数据层面。

在该项目中找到以下文件:

  • lib/src/model/data.dart:您将在此 Codelab 中修改的主文件。它包含用于从 Firestore 读写数据的所有逻辑。
  • web/index.html:浏览器为了启动应用而加载的文件。您可以通过修改此文件来安装和初始化 Web Firebase 库。

Firebase CLI

借助 Firebase 命令行界面 (CLI),您可以直接通过项目中的文件将 Web 应用和配置部署到 Firebase。

  1. 通过运行以下 npm 命令安装该 CLI:
npm -g install firebase-tools
  1. 通过运行以下命令验证该 CLI 是否已正确安装:
firebase --version

确保 Firebase CLI 版本为 7.4.0 或更高版本。

  1. 通过运行以下命令向 Firebase CLI 授权:
firebase login

您在上一步中克隆的代码库中已有 firebase.json 文件,其中包含一些现成的项目配置(其他配置文件的位置、托管部署等)。现在,您需要将应用的工作副本与您的 Firebase 项目相关联:

  1. 确保命令行可以访问应用的本地目录。
  2. 通过运行以下命令,将您的应用与 Firebase 项目相关联:
firebase use --add
  1. 当系统提示时,选择您的项目 ID,并为您的 Firebase 项目指定一个别名。

如果您有多个环境(生产、预演等),别名会非常有用。不过,在此 Codelab 中,只需使用 default 这个别名即可。

  1. 按照命令行中提供的说明操作。

为 Flutter 启用 Web 支持

如需将 Flutter 应用编译为在 Web 上运行,您必须启用此功能(目前为 Beta 版)。如需启用 Web 支持,请输入以下命令:

$ flutter channel beta
$ flutter upgrade
$ flutter config --enable-web

在 IDE 中的设备下拉列表下,或者在命令行中使用 flutter devices 后,您应该会看到列出了 Chrome网络服务器

Chrome 设备会自动启动 Chrome。网络服务器会启动托管该应用的服务器,以便您从任何浏览器加载该应用。

在开发过程中,请使用 Chrome 设备,以便使用开发者工具;如果想要在其他浏览器上进行测试,请使用网络服务器

在创建 Firebase 项目后,您可以配置一个(或多个)应用来使用该 Firebase 项目。您需要执行以下操作:

  • 在 Firebase 中为应用注册特定于平台的 ID。
  • 为您的应用生成配置文件。
  • 将配置添加到项目文件夹中的正确位置。

ac27fbbadff7a3b9.png

如果您要针对多个平台开发 Flutter 应用,则需要在同一个 Firebase 项目中注册您的应用将在其上运行的每个平台。

此 Codelab 侧重于 Web 平台,因为 Firebase for Flutter Codelab 中介绍了如何针对 iOS 和 Android 开发 Flutter 应用。如果您想为 FriendlyEats 应用添加 Android 或 iOS 支持,请转到相应 Codelab。

您的 Flutter 应用中有一个特殊的 web/index.html 文件;当该应用在 Web 上运行时,这个文件可用作入口点。您可以使用项目的特定配置修改该入口点,以便您的 Web 应用能够连接到 Firebase 后端。

针对 Web 进行配置

  1. Firebase 控制台中,选择左侧导航栏中的项目概览,然后点击将 Firebase 添加至您的应用即可开始使用下方的 Web 按钮。您应该会看到以下对话框:

f76ed55f71f15953.png

  1. 为应用指定一个别名。这个值用于在 Firestore 控制台中标识该应用的 Web 版本。
  2. 点击注册应用
  3. 注册应用后,添加 Firebase SDK 步骤中提供了一些代码,您需要将其粘贴到 Flutter 应用的 web/index.html 文件中。完成后,它应如下所示:

web/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>friendlyeats</title>

  <!-- The core Firebase JS SDK is always required and must be listed first -->
  <script src="https://www.gstatic.com/firebasejs/7.5.1/firebase-app.js"></script>

  <!-- TODO: Add SDKs for Firebase products that you want to use
      https://firebase.google.com/docs/web/setup#available-libraries -->

  <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
      apiKey: "YoUr_RaNdOm_API_kEy",
      authDomain: "your-project-name.firebaseapp.com",
      databaseURL: "https://your-project-name.firebaseio.com",
      projectId: "your-project-name",
      storageBucket: "your-project-name.appspot.com",
      messagingSenderId: "012345678901",
      appId: "1:109876543210:web:r4nd0mH3xH45h"
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
  </script>

</head>
<body>
  <script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
  1. 请注意,您刚刚粘贴的代码中有一个 TODO。您现在就可以解决这个问题。由于此 Codelab 使用 Firebase Authentication 和 Firestore,请立即为这些产品添加 script 标记:

web/index.html

  ...
  <!-- TODO: Add SDKs for Firebase products that you want to use
      https://firebase.google.com/docs/web/setup#available-libraries -->

  <script src="https://www.gstatic.com/firebasejs/7.5.1/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.5.1/firebase-firestore.js"></script>

  <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
      ...
  1. 保存您的 web/index.html 文件,然后在将 Firebase 添加到您的 Web 应用对话框中点击继续前往控制台
  2. 您的 Flutter 应用现已可以连接到 Firebase!

d43a19dbf1248134.png您发现了一些特别内容!

启用 Firebase 支持所需的大多数代码更改已签入您正在使用的项目中。不过,为了增加对移动平台的支持,您需要遵循与启用 Web 支持类似的流程:

  • 在 Firebase 项目中注册所需的平台
  • 下载特定于平台的配置文件,并将其添加到代码中。

Flutter 应用的顶层目录中有名为 iosandroid 的子目录。这两个目录分别用于存储特定于 iOS 和 Android 平台的配置文件。

配置 iOS

  1. Firebase 控制台中,选择左侧导航栏中的项目概览,然后点击将 Firebase 添加至您的应用即可开始使用下的“iOS”按钮。

您应该会看到以下对话框:

c42139f18fb9a2ee.png

  1. 要提供的重要值是 iOS 软件包 ID。只需执行接下来的三个步骤即可获得软件包 ID。
  1. 在命令行工具中,转到 Flutter 应用的顶层目录。
  2. 运行 open ios/Runner.xcworkspace 命令以打开 Xcode。
  1. 在 Xcode 中,点击左侧窗格中的顶级 Runner,以在右侧窗格中显示 General 标签页,如下所示。复制 Bundle Identifier 的值。

9733e26be329f329.png

  1. 返回 Firebase 对话框,将复制的软件包标识符粘贴到 iOS 软件包 ID 字段中,然后点击注册应用
  1. 还是在 Firebase 中,按照说明下载配置文件 GoogleService-Info.plist
  2. 返回 Xcode。请注意,Runner 也有一个名为 Runner 的子文件夹(如上一张图所示)。
  3. 将您刚刚下载的 GoogleService-Info.plist 文件拖到该 Runner 子文件夹中。
  4. 在 Xcode 中显示的对话框中,点击 Finish
  5. 返回 Firebase 控制台。在设置步骤中,点击下一步跳过其余的步骤,然后返回 Firebase 控制台的主页面。

您的 Flutter 应用已针对 iOS 配置完毕!

配置 Android

  1. Firebase 控制台中,选择左侧导航栏中的项目概览,然后点击将 Firebase 添加至您的应用即可开始使用下的 Android 按钮。

您应该会看到以下对话框:8254fc299e82f528.png

  1. 要提供的重要值是 Android 软件包名称。执行接下来的两个步骤即可获得软件包名称:
  1. 在 Flutter 应用目录中,打开 android/app/src/main/AndroidManifest.xml 文件。
  2. manifest 元素中,找到 package 属性的字符串值。这个值是 Android 软件包名称(类似于 com.yourcompany.yourproject)。复制这个值。
  3. 在 Firebase 对话框中,将复制的软件包名称粘贴到 Android 软件包名称字段中。
  4. 此 Codelab 不需要使用调试签名证书 SHA-1。将此项留空即可。
  5. 点击注册应用
  6. 还是在 Firebase 中,按照说明下载配置文件 google-services.json
  7. 转到 Flutter 应用目录,将您刚刚下载的 google-services.json 文件移至 android/app 目录中。
  8. 返回 Firebase 控制台,跳过其余的步骤,然后返回 Firebase 控制台的主页面。
  9. 所有 Gradle 配置均已签入。如果您的应用仍在运行,请将其关闭并重新构建,以允许 Gradle 安装依赖项。

您的 Flutter 应用已针对 Android 配置完毕!

您已准备好实际处理应用了!首先,在本地运行应用。现在,您可以在任何已配置(并且您拥有设备和模拟器)的平台上运行应用。

使用以下命令了解有哪些设备可用:

flutter devices

上述命令的输出类似于如下所示,具体取决于有哪些设备可用:

3 connected devices:

Android SDK built for x86 • emulator-5554 • android-x86    • Android 7.1.1 (API 25) (emulator)
Chrome                    • chrome        • web-javascript • Google Chrome 79.0.3945.130
Web Server                • web-server    • web-javascript • Flutter Tools

我们将使用 chrome 设备继续学习此 Codelab。

  1. 运行以下 Flutter CLI 命令:
flutter run -d chrome
  1. Flutter 的起始步骤是构建您的 Web 应用,它会自动打开一个带有运行中应用的 Chrome 窗口。

现在,您应该会看到,FriendlyEats 的副本已连接到您的 Firebase 项目。

该应用会自动连接到您的 Firebase 项目,并使您以匿名用户身份静默登录。

c45806a2ac9300d9.png

在本部分,您需要将一些数据写入 Cloud Firestore,以填充应用的界面。此操作可以使用 Firebase 控制台手动完成,但在这里,您需要在应用中完成,目的是查看基本 Cloud Firestore 写入过程的演示。

数据模型

Firestore 数据分为集合、文档、字段和子集合。每家餐厅都以文档形式存储在名为 restaurants 的顶级集合中。

92f8dc2c769d2d6c.png

之后,您将每条评价存储在各 restaurant 内名为 ratings 的子集合中。

a00d9eb006ddd6c0.png

向 Firestore 添加餐厅

该应用中的主要模型对象是餐厅。接下来,您需要编写一些代码,用于将餐厅文档添加到 restaurants 集合中。

  1. 打开 lib/src/model/data.dart
  2. 找到 addRestaurant 函数。
  3. 将整个函数替换为以下代码。

lib/src/model/data.dart

Future<void> addRestaurant(Restaurant restaurant) {
  final restaurants = FirebaseFirestore.instance.collection('restaurants');
  return restaurants.add({
    'avgRating': restaurant.avgRating,
    'category': restaurant.category,
    'city': restaurant.city,
    'name': restaurant.name,
    'numRatings': restaurant.numRatings,
    'photo': restaurant.photo,
    'price': restaurant.price,
  });
}

上述代码用于将新文档添加到 restaurants 集合中。

为此,请首先设置一项对 Cloud Firestore 集合 restaurants 的引用,然后通过 adding 添加数据。

文档数据来自 Restaurant 对象,该对象需要转换为 Firestore 插件的 Map

添加一些餐厅

  1. 重新构建和刷新 Flutter 应用(在运行应用的终端窗口中按“Shift + R”)。
  2. 点击 ADD SOME

该应用会自动生成一组随机的 restaurants 对象,并调用您的 addRestaurant 函数。不过,您不会在自己的 Web 应用中看到数据,因为您仍需实现数据的检索过程(此 Codelab 的下一部分)。

如果您转到 Firebase 控制台中的“开发”>“数据库”>“Cloud Firestore”标签页,应该会在 restaurants 集合中看到新文档!

f06898b9d6dd4881.png

恭喜!您刚刚从 Web 应用向 Cloud Firestore 写入了数据!

在下一部分,您将了解如何从 Cloud Firestore 检索数据,并在您的应用中显示这些数据。

在本部分,您将了解如何从 Cloud Firestore 检索数据,并在您的应用中显示这些数据。其中的关键两步是创建查询以及监听其快照 Stream。此监听器会收到与查询匹配的所有现有数据的通知,并实时接收更新。

首先,构建查询,用于提供默认的、未经过滤的餐厅列表。

  1. 返回 lib/src/model/data.dart 文件。
  2. 找到 loadAllRestaurants 函数。
  3. 将整个函数替换为以下代码。

lib/src/model/data.dart

Stream<QuerySnapshot> loadAllRestaurants() {
  return FirebaseFirestore.instance
      .collection('restaurants')
      .orderBy('avgRating', descending: true)
      .limit(50)
      .snapshots();
}

上述代码构建了一个查询,可从名为 restaurants 的顶级集合中检索最多 50 家餐厅,并按它们的平均评分(当前均为零)排序。

现在,您需要将从 Stream 返回的每个 QuerySnapshot 转换为可呈现的 Restaurant 数据。

如需从 restaurants 集合的 QuerySnapshot 中提取 Restaurant 信息,请执行以下操作:

  1. 返回 lib/src/model/data.dart 文件。
  2. 找到 getRestaurantsFromQuery 函数。
  3. 将整个函数替换为以下代码:

lib/src/model/data.dart

List<Restaurant> getRestaurantsFromQuery(QuerySnapshot snapshot) {
  return snapshot.docs.map((DocumentSnapshot doc) {
    return Restaurant.fromSnapshot(doc);
  }).toList();
}

每当您之前创建的 Query 有新的 QuerySnapshot 时,就会调用 getRestaurantsFromQuery 方法。QuerySnapshots 是 Firestore 用来向您的应用实时通知 Query 更改的一种机制。

此方法会直接将 snapshot 中包含的所有 documents 转换为可在 Flutter 应用的其他位置使用的 Restaurant 对象。

现在,您已经实现了这两种方法,接下来,请重新构建和重新加载应用,并验证您之前在 Firebase 控制台中看到的餐厅是否会出现在该应用中。如果您成功完成这一部分,您的应用会使用 Cloud Firestore 读取和写入数据!

随着您的餐厅列表发生变化,此监听器会自动更新。请尝试转至 Firebase 控制台并手动删除一家餐厅或更改其名称,您会发现相应的更改会立即显示在您的网站上!

edd9adbafa5bd539.png

到目前为止,您了解了如何使用 onSnapshot 实时检索更新;不过,这未必始终符合您的要求。有时,仅提取一次数据是一种有意义的做法。

您需要一种方法,可以在用户于该应用中点击特定餐厅时通过 ID 加载相应的餐厅。

  1. 返回 lib/src/model/data.dart 文件。
  2. 找到 getRestaurant 函数。
  3. 将整个函数替换为以下代码:

lib/src/model/data.dart

Future<Restaurant> getRestaurant(String restaurantId) {
  return FirebaseFirestore.instance
      .collection('restaurants')
      .doc(restaurantId)
      .get()
      .then((DocumentSnapshot doc) => Restaurant.fromSnapshot(doc));
}

此代码使用 get() 来检索 Future<DocumentSnapshot>,其中包含您所请求的餐厅的信息。您只需通过 then() 将其传递到一个函数中,该函数会在 DocumentSnapshot 准备就绪后将其转换为 Restaurant 对象。

实现此方法后,您可以查看每家餐厅的详情页面。

  1. 在运行 flutter 的终端中按“Shift + R”,以刷新应用。
  2. 点击列表中的一家餐厅,您应该能看到该餐厅的详情页面:

f8ca540dda5540a9.png

接下来,您需要添加使用事务功能为餐厅评分所需的代码。

在本部分,您将添加一项功能:让用户能向餐厅提交评价。到目前为止,您的所有写入都是原子性的,也比较简单。如果有任何写入出错,您可能只需提示用户重试写入,或者您的应用会自动重试写入。

许多用户在使用您的应用时,都会希望为餐厅添加评分,因此您需要协调多次读写操作。首先是用户提交评价,然后您需要更新相应餐厅的评分 countaverage rating。如果其中一个操作失败,而另一个成功,应用就会处于不一致的状态。数据库某个部分的数据与另一部分的数据不匹配。

幸运的是,Cloud Firestore 提供事务功能,支持您在单个原子操作中执行多个读写操作,确保您的数据保持一致。

  1. 返回 lib/src/model/data.dart 文件。
  2. 找到 addReview 函数。
  3. 将整个函数替换为以下代码:

lib/src/model/data.dart

Future<void> addReview({String restaurantId, Review review}) {
  final restaurant =
      FirebaseFirestore.instance.collection('restaurants').doc(restaurantId);
  final newReview = restaurant.collection('ratings').doc();

  return FirebaseFirestore.instance.runTransaction((Transaction transaction) {
    return transaction
        .get(restaurant)
        .then((DocumentSnapshot doc) => Restaurant.fromSnapshot(doc))
        .then((Restaurant fresh) {
      final newRatings = fresh.numRatings + 1;
      final newAverage =
          ((fresh.numRatings * fresh.avgRating) + review.rating) / newRatings;

      transaction.update(restaurant, {
        'numRatings': newRatings,
        'avgRating': newAverage,
      });

      transaction.set(newReview, {
        'rating': review.rating,
        'text': review.text,
        'userName': review.userName,
        'timestamp': review.timestamp ?? FieldValue.serverTimestamp(),
        'userId': review.userId,
      });
    });
  });
}

上面的函数会触发一项事务,该事务首先获取 Restaurantfresh 版本,由 restaurantId 表示。

然后,您将更新 restaurant 文档引用中 avgRatingnumRatings 的数值。

同时,您需要通过 newReview 文档引用将新的 review 添加到相应餐厅的 ratings 子集合中。

如需测试刚刚添加的代码,请执行以下操作:

  1. 在运行 flutter 的终端中按“Shift + R”,以刷新应用。
  2. 转到任意餐厅详情页面。
  3. 添加一些评价。为此,您可以执行以下操作:
  • 如果列表中没有评价,则点击空列表中的 ADD SOME 按钮
  • 点击 + 悬浮操作按钮,然后输入您自己的评价

目前,该应用显示了餐厅列表,但用户无法根据自己的需要进行过滤。在本部分,您将使用 Cloud Firestore 的高级查询功能来进行过滤。

下面是一个提取所有 Dim Sum 餐厅的简单查询示例:

Query filteredCollection = FirebaseFirestore.instance
        .collection('restaurants')
        .where('category', isEqualTo: 'Dim Sum');

顾名思义,where() 方法支持查询仅下载集合中其字段满足您所设限制条件的成员。在本示例中,它仅下载符合 category 等于 Dim Sum 这一条件的餐厅。

同样,您可以对返回的数据排序:

Query filteredAndSortedCollection = FirebaseFirestore.instance
        .collection('restaurants')
        .where('category', isEqualTo: 'Dim Sum')
        .orderBy('price', descending: true);

orderBy() 方法支持查询按 price 属性(从高到低)对 Dim Sum 餐厅排序。

在该应用中,用户可以通过串连多个过滤条件(按热门程度排序)来创建特定查询,例如:Pizza in San FranciscoSeafood in Los Angeles

您可以创建一个方法,构建一个根据用户所选的多个条件过滤餐厅的查询。

  1. 返回 lib/src/model/data.dart 文件。
  2. 找到 loadFilteredRestaurants 函数。
  3. 将整个函数替换为以下代码:

lib/src/model/data.dart

Stream<QuerySnapshot> loadFilteredRestaurants(Filter filter) {
  Query collection = FirebaseFirestore.instance.collection('restaurants');
  if (filter.category != null) {
    collection = collection.where('category', isEqualTo: filter.category);
  }
  if (filter.city != null) {
    collection = collection.where('city', isEqualTo: filter.city);
  }
  if (filter.price != null) {
    collection = collection.where('price', isEqualTo: filter.price);
  }
  return collection
      .orderBy(filter.sort ?? 'avgRating', descending: true)
      .limit(50)
      .snapshots();
}

上述代码添加了多个 where 过滤条件和一个 orderBy 子句,用于基于用户输入构建复合查询。现在,该查询仅返回符合用户要求的餐厅。

在运行 flutter 的终端中按“Shift + R”,以在浏览器中刷新应用。

现在,请尝试按价格、城市和类别过滤。在测试过程中,您会在浏览器的 JavaScript 控制台中看到错误,具体如下所示:

The query requires an index. You can create it here: https://console.firebase.google.com/project/.../database/firestore/indexes?create_index=...

之所以会出现这些错误,是因为 Cloud Firestore 要求为大多数复合查询建立索引。要求为查询建立索引可确保 Cloud Firestore 大规模地快速运行。

从错误消息中打开链接会自动在 Firebase 控制台中打开索引创建界面,并填充正确的参数。

在下一部分,您将通过 Firebase CLI 一次性编写和部署此应用所需的索引。

如果您不想探索应用中的每条路径,也不想按照各个索引创建链接执行操作,可以使用 Firebase CLI 轻松部署多个索引。

  1. 在应用项目的根目录中,找到 firestore.indexes.json 文件。

此文件描述了所有可能的过滤条件组合需要的全部索引。

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. 使用以下命令可部署这些索引:
firebase deploy --only firestore:indexes

几分钟后,您的索引将变为活跃状态,错误消息将消失。如果您在索引完全就绪之前尝试使用它们,则可能会看到类似以下内容的错误:

The query requires an index. That index is currently building and cannot be used yet. See its status here:
https://console.firebase.google.com/project/.../database/firestore/indexes?create_index=...

在此 Codelab 的开头,您已将该应用的安全规则设置为完全开放数据库的任何读写操作。在实际应用中,您可以通过设置更精细的规则来阻止预期之外的数据访问或修改

.

  1. 在 Firebase 控制台的开发部分,点击数据库
  2. 点击“Cloud Firestore”部分中的规则标签页(或直接转到 Firebase 控制台)。
  3. 将默认设置替换为以下规则,然后点击发布

firestore.rules

service cloud.firestore {
  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo)
    //   - Validate updates
    //   - Deletes are not allowed
    match /restaurants/{restaurantId} {
      allow read, create: if request.auth != null;
      allow update: if request.auth != null
                    && request.resource.data.name == resource.data.name
      allow delete: if false;

      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
        allow update, delete: if false;
      }
    }
  }
}

这些规则用于限制访问,以确保客户端仅进行安全的更改,例如:

  • 对餐厅文档的更新只能更改评分,不能更改名称或任何其他不可变数据。
  • 只有当用户 ID 与登录的用户相匹配时,才能创建评分,这样可以防止仿冒攻击。

您可以使用 Firebase CLI 将规则部署到您的 Firebase 项目,而不是使用 Firebase 控制台。您的工作目录中的 firestore.rules 文件已包含上述规则。如需从本地文件系统部署这些规则(而不是使用 Firebase 控制台),请运行以下命令:

firebase deploy --only firestore:rules

flutter build web

到目前为止,您仅使用了 Flutter 应用的“调试”版本。这些 build 的速度有点慢,因为它们含有可简化调试的额外信息。

在部署应用之前,您需要构建一个正式 (prod) 版本。Flutter 支持您使用 build 工具构建正式版应用:

flutter build web

此操作会将所有为正式版构建的资源放入项目的 build/web 目录中。

您的应用现已准备就绪,可以部署到 Firebase!

firebase deploy

您的项目已经过预先配置,可以构建和部署 Flutter 生成的资源(请查看项目根目录下的 firebase.json 文件)。

使用以下命令,借助 Firebase 部署应用的新版本:

firebase init hosting
firebase deploy --only hosting

上述过程应该只需几秒钟即可完成。它会清理之前的所有 build (flutter clean)、重新构建您的应用 (flutter build web),并将刚构建的资源(build/web 的内容)部署到 Firebase Hosting。

成功消息包含托管网址,说明已发布应用现已发布到互联网上!

恭喜!

在此 Codelab 中,您了解了如何使用 Firebase Authentication 和 Firestore 插件将您的 Flutter Web 应用连接到 Firebase,使用 Cloud Firestore 执行了基本和高级读写操作,还借助安全规则保护了数据访问权限。

您可以在代码库的 done 分支中找到完整解决方案。

如需详细了解 Dart 和 Flutter,请访问其官方网站:

如需详细了解 Cloud Firestore,请参阅以下资源:

如需了解其他 Firestore(还有 Firebase!)功能,此 Codelab 可作为一个很好的着手点。如果您想要接受其他挑战,不妨尝试下列操作:

  • 使用 addRestaurantsBatch 方法中的批量写入在单个请求中添加所有餐厅和评价,以使应用的界面只更新一次。
  • 修改 RestaurantAppBar 微件,以便在用户添加评价时实时更新相应餐厅的星级。
  • google_sign_in 启用 firebase_auth,以检索发布评论的用户的名字,因为所有用户当前都是匿名用户。