使用 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” 主屏幕

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

  • 使用电子邮件地址和密码对用户进行身份验证
  • 向 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. 点击下一步。由于 Firebase SDK 已包含在示例项目的 build.gradle 文件中,因此请点击下一步跳转到后续步骤
  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,您将向存储表示 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集合中的一个文档表示,每项任务都有一个名为 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 有多种用途,从远程更改应用外观到为不同的细分用户群配置不同的行为,不一而足。在本 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 应用!

您向一个完全使用 Jetpack Compose 为界面构建的 Android 应用添加了 Firebase Authentication、Performance Monitoring、Remote Config 和 Cloud Firestore,并使该应用能够融入推荐的 MVVM 架构!

深入阅读

参考文档