使用 Firebase 和 Jetpack Compose 构建 Android 应用

1. 简介

上次更新日期:2022 年 11 月 16 日

使用 Firebase 和 Jetpack Compose 构建 Android 应用

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

若要了解 Firebase 和 Jetpack Compose 如何协同工作,首先要理解现代 Android 架构。良好的架构能够非常清晰地说明各组件的组织方式和相互通信方式,因此系统易于理解、易于开发和维护。在 Android 领域,推荐的架构称为模型 - 视图 - 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. 点击下一步。由于 Firebase SDK 已包含在示例项目的 build.gradle 文件中,因此请点击下一步跳转到后续步骤
  5. 点击继续前往控制台以完成操作。

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

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

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

  1. 构建菜单中,选择 Firestore,然后点击创建数据库
  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 集合中。如果您在使用同一账号登录的其他设备上登录轻松搞定,则可以修改待办事项,并观察所有设备上的待办事项实时更新情况。

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 中被调用。这是确保所有屏幕从一开始就提供最新值的最佳方式。如果您稍后在用户执行某项操作时更改界面或应用的行为,将会给用户带来不好的体验!

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

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,在点击三点状图标时,您应该会看到三个选项;

请尝试在控制台中多次更改此值,然后重启应用。使用 Remote Config 在您的应用中启动新功能就是这么简单!

7. 恭喜

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

您已将 Firebase Authentication、Performance Monitoring、Remote Config 和 Cloud Firestore 添加到一个完全使用 Jetpack Compose 为界面构建的 Android 应用,并将其融入推荐的 MVVM 架构中!

深入阅读

参考文档