使用 Firebase 和 Jetpack Compose 构建 Android 应用

1. 简介

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

使用 Firebase 和 Jetpack Compose 构建 Android 应用

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

若要了解 Firebase 和 Jetpack Compose 能否很好地协同工作,第一步是了解现代 Android 架构。良好的架构会让系统易于理解、易于开发和维护,因为它会非常明确地说明组件的组织方式和相互通信的方式。在 Android 中,推荐的架构称为“模型 - 视图 - ViewModel”。Model 表示在应用中访问数据的层。View 是界面层,应该对业务逻辑一无所知。ViewModel 是应用业务逻辑的位置,有时需要 ViewModel 调用 Model 层。

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

构建内容

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

“Make it So Add Task”屏幕 让它成为主屏幕

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

  • 使用电子邮件地址和密码对用户进行身份验证
  • 向 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 文件中,请点击 Next 跳至后续步骤
  5. 点击继续前往控制台即可完成。

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

  1. Build 菜单中,选择 Authentication,然后点击 Get Started
  2. 登录方法卡片中,选择电子邮件地址/密码并启用它。
  3. 接下来,点击 Add new provider,然后选择并启用 Anonymous

接下来,设置 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 集合添加一个监听器,用来存储代表Make it So 中显示的任务的文档。添加此监听器后,您就会收到对此集合所做的每项更新。

是时候编写代码了!

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 提供了自定义跟踪记录。按照Make it So 中介绍的后续步骤来添加自定义跟踪记录并衡量不同代码段的性能。

是时候编写代码了!

打开 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 控制台并前往性能信息中心。在屏幕底部,您会看到三个标签页:网络请求自定义跟踪记录屏幕呈现

转到 Custom trace 标签页,然后检查您在代码库中添加的跟踪记录是否显示在代码库中,以及您是否了解执行这些代码段通常需要多长时间。

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 中调用该函数。这样做是确保所有屏幕从一开始就显示最新值的最佳方式。如果您稍后在用户执行某项操作时更改界面或应用行为,这对用户体验而言并不好!

第二个函数会返回您为刚刚在控制台中创建的参数发布的布尔值。您还需要将以下代码添加到 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 架构!

深入阅读

参考文档