使用 Firebase 和 Jetpack Compose 构建 Android 应用

1. 简介

上次更新时间:2022 年 11 月 16 日

使用 Firebase 和 Jetpack Compose 构建 Android 应用

在此 Codelab 中,您将构建一个名为 Make It So 的 Android 应用。该应用的界面完全使用 Jetpack Compose 构建而成,它是 Android 用于构建原生界面的现代化工具包,直观易用,所需的代码量比编写 .xml 文件并将其绑定到 activity、fragment 或 View 要少。

若要了解 Firebase 和 Jetpack Compose 如何协同工作,首先需要了解现代 Android 架构。良好的架构能够非常清晰地说明各组件的组织方式和相互通信方式,因此系统易于理解、易于开发和维护。在 Android 领域,推荐的架构称为模型 - View - ViewModel模型表示用于访问应用中数据的层。View 是界面层,对业务逻辑一无所知。ViewModel 是应用业务逻辑的位置,有时需要 ViewModel 调用 Model 层。

我们强烈建议您阅读这篇文章,了解如何将 Model - View - ViewModel 应用于使用 Jetpack Compose 构建的 Android 应用,因为这会使代码库更易于理解,并使后续步骤更易于完成。

构建内容

Make It So 是一款简单的待办事项列表应用,可让用户添加和编辑任务,添加标记、优先级和截止日期,以及将任务标记为已完成。以下图片显示了此应用的两个主要页面:任务创建页面和包含已创建任务列表的主页面。

“添加任务”屏幕 设为主屏幕

您将添加此应用中缺少的一些功能:

  • 使用电子邮件地址和密码对用户进行身份验证
  • 向 Firestore 集合添加监听器,并让界面对更改做出响应
  • 添加自定义跟踪记录以监控应用中特定代码的性能
  • 使用 Remote Config 创建功能切换开关,并使用分阶段发布模式发布该功能

学习内容

  • 如何在现代 Android 应用中使用 Firebase Authentication、Performance Monitoring、Remote Config 和 Cloud Firestore
  • 如何将 Firebase API 融入 MVVM 架构
  • 如何在 Compose 界面中反映使用 Firebase API 进行的更改

所需条件

2. 获取示例应用并设置 Firebase

获取示例应用的代码

从命令行克隆 GitHub 代码库:

git clone https://github.com/FirebaseExtended/make-it-so-android.git

设置 Firebase

首先,前往 Firebase 控制台,然后点击“+ 添加项目”创建一个 Firebase 项目按钮,如下所示:

Firebase 控制台

按照屏幕上的步骤完成项目创建。

在每个 Firebase 项目中,您可以创建不同的应用:Android、iOS、Web、Flutter 和 Unity。选择“Android”选项,如下所示:

Firebase 项目概览

然后按以下步骤操作:

  1. 输入 com.example.makeitso 作为软件包名称,您还可以输入别名。对于本 Codelab,您无需添加调试签名证书。
  2. 点击下一步,注册您的应用并访问 Firebase 配置文件。
  3. 点击下载 google-services.json 以下载配置文件,并将其保存在 make-it-so-android/app 目录中。
  4. 点击下一步。由于示例项目的 build.gradle 文件中已包含 Firebase SDK,因此请点击下一步跳至后续步骤
  5. 点击继续前往控制台以完成操作。

为使 Make it So 应用正常工作,在跳转到代码之前,您需要在控制台中完成两项操作:启用身份验证提供方并创建 Firestore 数据库。首先,启用 Authentication,以便用户可以登录应用:

  1. Build 菜单中,选择 Authentication,然后点击 Get Started(开始)。
  2. 登录方法卡片中,选择电子邮件地址/密码,然后将其启用。
  3. 接下来,点击添加新提供商,然后选择并启用匿名

接下来,设置 Firestore。您将使用 Firestore 来存储已登录用户的任务。每个用户都会在数据库的集合中获得自己的文档

  1. Build 菜单中,选择 Firestore,然后点击 Create database(创建数据库)。
  2. 以生产模式启动保持启用状态,然后点击下一步
  3. 出现提示时,选择 Cloud Firestore 数据的存储位置。开发正式版应用时,您需要将其部署在靠近大多数用户且与其他 Firebase 服务(例如 Functions)共用同一区域的位置。对于此 Codelab,您可以保留默认区域或选择离您最近的区域。
  4. 点击启用以预配 Firestore 数据库。

我们花点时间为 Firestore 数据库构建强大的安全规则。打开 Firestore 信息中心,然后转到规则标签页。然后更新安全规则,使其如下所示:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow create: if request.auth != null;
      allow read, update, delete: if request.auth != null && resource.data.userId == request.auth.uid;
    }
  }
}

这些规则基本上意味着,应用的任何已登录用户都可以在任何集合中为自己创建文档。然后,创建文档后,只有创建该文档的用户才能查看、更新或删除该文档。

运行应用

现在,您可以运行应用了!在 Android Studio 中打开 make-it-so-android/start 文件夹并运行应用(可以使用 Android 模拟器或真实的 Android 设备完成)。

3. Firebase 身份验证

您要添加哪项功能?

Make It So 示例应用的当前状态下,用户无需先登录即可开始使用该应用。它使用匿名身份验证来实现此目的。不过,匿名账号不允许用户在其他设备上或甚至在未来的会话中访问其数据。尽管匿名身份验证有助于预热初始配置,但您应始终为用户提供转换为其他登录方式的选项。因此,在此 Codelab 中,您将向 Make It So 应用添加电子邮件地址和密码身份验证。

开始编码吧!

用户创建账号后,您需要通过输入电子邮件地址和密码向 Firebase Authentication API 请求电子邮件凭据,然后将新凭据与匿名账号相关联。在 Android Studio 中打开 AccountServiceImpl.kt 文件,并更新 linkAccount 函数,使其如下所示:

model/service/impl/AccountServiceImpl.kt

override suspend fun linkAccount(email: String, password: String) {
    val credential = EmailAuthProvider.getCredential(email, password)
    auth.currentUser!!.linkWithCredential(credential).await()
}

现在打开 SignUpViewModel.kt,并在 onSignUpClick 函数的 launchCatching 代码块中调用服务 linkAccount 函数:

screens/sign_up/SignUpViewModel.kt

launchCatching {
    accountService.linkAccount(email, password)
    openAndPopUp(SETTINGS_SCREEN, SIGN_UP_SCREEN)
}

首先,它会尝试进行身份验证,如果调用成功,则会进入下一个屏幕 (SettingsScreen)。当您在 launchCatching 块内执行这些调用时,如果第一行发生错误,系统将捕获并处理异常,而根本不会到达第二行。

再次打开 SettingsScreen 后,您需要确保 Sign in(登录)和 Create account(创建账号)选项都不见,因为用户现已通过身份验证。为此,我们让 SettingsViewModel 监听当前用户的状态(在 AccountService.kt 中提供),以检查账号是否为匿名账号。为此,请更新 SettingsViewModel.kt 中的 uiState,使其如下所示:

screens/settings/SettingsViewModel.kt

val uiState = accountService.currentUser.map {
    SettingsUiState(it.isAnonymous)
}

最后,您需要更新 SettingsScreen.kt 中的 uiState,以收集 SettingsViewModel 发出的状态:

screens/settings/SettingsScreen.kt

val uiState by viewModel.uiState.collectAsState(
    initial = SettingsUiState(false)
)

现在,每当用户发生变化时,SettingsScreen 都会自行重组,以根据用户的新身份验证状态显示选项。

测试时间!

运行 Make it So,然后点击屏幕右上角的齿轮图标以转到设置。然后,点击“创建账号”选项:

“Make it So”设置屏幕 Make it So 注册页面

输入有效的电子邮件地址和安全系数高的密码以创建账号。系统应该可以正常运行,并且应该会将您重定向至设置页面,您会在该页面上看到两个新选项:退出和删除账号。您可以点击“用户”标签页,查看在 Firebase 控制台的“Authentication”信息中心内创建的新账号。

4. Cloud Firestore

您要添加哪项功能?

对于 Cloud Firestore,您将向 Firestore 集合添加一个监听器,以存储文档,这些文档代表按任务执行中所示的任务。添加此监听器后,您将收到对该集合所做的所有更新。

是时候开始编码了!

更新 StorageServiceImpl.kt 中提供的 Flow,使其如下所示:

model/service/impl/StorageServiceImpl.kt

override val tasks: Flow<List<Task>>
    get() =
      auth.currentUser.flatMapLatest { user ->
        firestore.collection(TASK_COLLECTION).whereEqualTo(USER_ID_FIELD, user.id).dataObjects()
      }

此代码将根据 user.id 向任务集合添加监听器。每个任务都由名为 tasks集合中的文档表示,并且每个文档都有一个名为 userId 的字段。请注意,如果 currentUser 的状态发生变化(例如,通过退出账号),系统会发出新的 Flow

现在,您需要让 TasksViewModel.kt 中的 Flow 反映的内容与服务中的内容相同:

screens/tasks/TasksViewModel.kt

val tasks = storageService.tasks

最后,让 TasksScreens.kt 中的 composable function(表示界面)了解此流程并将其收集为状态。每当状态发生变化时,可组合函数都会自动重组自身,并向用户显示最新状态。将以下代码添加到 TasksScreen composable function

screens/tasks/TasksScreen.kt

val tasks = viewModel
    .tasks
    .collectAsStateWithLifecycle(emptyList())

可组合函数有权访问这些状态后,您可以更新 LazyColumn(您在屏幕上显示列表的结构),如下所示:

screens/tasks/TasksScreen.kt

LazyColumn {
    items(tasks.value, key = { it.id }) { taskItem ->
        TaskItem( [...] )
    }
}

测试时间!

为了测试其是否有效,请使用该应用添加新任务(点击屏幕右下角的“添加”按钮)。创建完任务后,该任务应会显示在 Firestore 控制台中的 Firestore 集合中。如果您在其他设备上使用同一账号登录 Make it So,则可以修改待办事项,并在所有设备上实时查看这些事项的更新情况。

5. 性能监控

您要添加哪项功能?

性能非常重要,需要引起重视,因为如果应用性能不佳,并且用户使用应用完成简单任务所需的时间过长,他们很可能会放弃使用您的应用。正因如此,有时收集一些有关用户在应用中完成的特定历程的相关指标会很有用。为了帮助您实现这一目标,Firebase Performance Monitoring 提供了自定义跟踪记录。按照成就梦想中的后续步骤,添加自定义轨迹并衡量不同代码段的性能。

开始编码吧!

如果您打开 Performance.kt 文件,会看到一个名为 trace 的内嵌函数。此函数会调用 Performance Monitoring API 来创建自定义轨迹,并将轨迹名称作为参数传递。您看到的另一个参数是您要监控的代码块。为每个跟踪记录收集的默认指标是完全运行所需的时间:

model/service/Performance.kt

inline fun <T> trace(name: String, block: Trace.() -> T): T = Trace.create(name).trace(block)

您可以选择您认为重要的代码库部分,并为其添加自定义轨迹。以下示例展示了如何将自定义跟踪记录添加到您之前在本 Codelab 中(在 AccountServiceImpl.kt 中)看到的 linkAccount 函数:

model/service/impl/AccountServiceImpl.kt

override suspend fun linkAccount(email: String, password: String): Unit =
  trace(LINK_ACCOUNT_TRACE) {
      val credential = EmailAuthProvider.getCredential(email, password)
      auth.currentUser!!.linkWithCredential(credential).await()
  }

现在该您亲自试试了!向 Make it So 应用添加一些自定义轨迹,然后继续下一部分,测试其是否按预期运行。

测试时间!

添加完自定义轨迹后,运行应用并确保使用要衡量的功能几次。然后,前往 Firebase 控制台,前往性能信息中心。屏幕底部有三个标签页:网络请求自定义轨迹屏幕渲染

转到自定义跟踪记录标签页,检查您在代码库中添加的跟踪记录是否显示在此处,以及您是否可以看到执行这些代码段通常需要多长时间。

6. Remote Config

您要添加哪项功能?

Remote Config 有多种用途,从远程更改应用的外观,到为不同的细分用户群配置不同的行为,不一而足。在本 Codelab 中,您将使用 Remote Config 创建一个功能切换开关,用于在 Make it So 应用中显示或隐藏新的修改任务功能。

开始编码吧!

首先,您需要在 Firebase 控制台中创建配置。为此,您需要前往 Remote Config 信息中心,然后点击添加参数按钮。根据下图填写相应字段:

“Remote Config 的“创建参数”对话框

填写完所有字段后,您可以点击保存按钮,然后点击发布。现在,参数已创建完毕且可供代码库使用,接下来您需要添加用于将新值提取到应用的代码。打开 ConfigurationServiceImpl.kt 文件并更新这两个函数的实现:

model/service/impl/ConfigurationServiceImpl.kt

override suspend fun fetchConfiguration(): Boolean {
  return remoteConfig.fetchAndActivate().await()
}

override val isShowTaskEditButtonConfig: Boolean
  get() = remoteConfig[SHOW_TASK_EDIT_BUTTON_KEY].asBoolean()

第一个函数会从服务器提取值,应用启动后,该函数会在 SplashViewModel.kt 中被调用。这是确保从一开始所有屏幕中都显示最新值的最佳方式。如果您在用户执行操作期间更改应用的界面或行为,将会给用户带来不良体验!

第二个函数会返回您刚在控制台中创建的参数所发布的布尔值。您需要在 TasksViewModel.kt 中检索此信息,方法是将以下代码添加到 loadTaskOptions 函数中:

screens/tasks/TasksViewModel.kt

fun loadTaskOptions() {
  val hasEditOption = configurationService.isShowTaskEditButtonConfig
  options.value = TaskActionOption.getOptions(hasEditOption)
}

您将检索第一行中的值,并使用该值在第二行中加载任务项的菜单选项。如果值为 false,则表示菜单不包含修改选项。现在您已经有了选项列表,接下来需要让界面正确显示它。使用 Jetpack Compose 构建应用时,您需要查找声明 TasksScreen 界面的 composable function。因此,请打开 TasksScreen.kt 文件并更新 LazyColum 以指向 TasksViewModel.kt 中提供的选项:

screens/tasks/TasksScreen.kt

val options by viewModel.options

LazyColumn {
  items(tasks.value, key = { it.id }) { taskItem ->
    TaskItem(
      options = options,
      [...]
    )
  }
}

TaskItem 是另一个 composable function,用于声明单个任务的界面。每个任务都有一个包含选项的菜单,当用户点击该任务末尾的三点状图标时,系统会显示相应选项。

开始测试吧!

现在,您可以运行应用了!检查您使用 Firebase 控制台发布的值是否与应用的行为一致:

  • 如果是 false,则点击三点状图标时应该只会看到两个选项;
  • 如果是 true,在点击三点状图标时,您应该会看到三个选项;

尝试在 Play 管理中心内多次更改该值,然后重启应用。使用 Remote Config 在应用中发布新功能就是如此简单!

7. 恭喜

恭喜,您已成功使用 Firebase 和 Jetpack Compose 构建了一个 Android 应用!

您已将 Firebase Authentication、Performance Monitoring、Remote Config 和 Cloud Firestore 添加到完全使用 Jetpack Compose 构建的 Android 应用的界面中,并使其符合建议的 MVVM 架构!

深入阅读

参考文档