使用 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 世界中,推荐的架构称为 Model - View - ViewModel模型表示用于访问应用中数据的层。View 是界面层,不应了解任何业务逻辑。ViewModel 是应用业务逻辑的位置,这有时需要 ViewModel 调用 Model 层。

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

构建内容

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

Make it So 添加任务界面 “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 控制台

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

将 Android 应用添加到 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 数据库。

设置身份验证

首先,我们来启用身份验证,以便用户能够登录应用:

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

设置 Cloud Firestore

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

  1. 在 Firebase 控制台的左侧面板中,展开构建,然后选择 Firestore 数据库
  2. 点击创建数据库
  3. 数据库 ID 设置为 (default)
  4. 为数据库选择一个位置,然后点击下一步
    对于真实应用,您需要选择靠近用户的位置。
  5. 点击以测试模式启动。阅读有关安全规则的免责声明。
    在本部分的后续步骤中,您将添加安全规则来保护您的数据。在没有为数据库添加安全规则的情况下,请不要公开分发或公开应用。
  6. 点击创建

我们花点时间为 Firestore 数据库构建强大的安全规则。

  1. 打开 Firestore 信息中心,然后前往规则标签页。
  2. 更新安全规则,使其如下所示:
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 Authentication

您要添加哪项功能?

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 后,您需要确保“登录”和“创建账号”选项已消失,因为用户现在已通过身份验证。为此,我们将让 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,您将向存储表示 Make it So 中显示的任务的文档的 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 集合添加监听器。每个任务都由名为 tasks集合中的文档表示,并且每个文档都有一个名为 userId 的字段。请注意,如果 currentUser 的状态发生变化(例如,通过退出账号),系统会发出新的 Flow

现在,您需要使 TasksViewModel.kt 中的 Flow 与服务中的 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 中之前看到的 linkAccount 函数(在 AccountServiceImpl.kt 中)添加自定义轨迹:

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 架构!

深入阅读

参考文档