项目:Forage 应用

1. 准备工作

此 Codelab 将介绍如何自行构建一个名为 Forage 的新应用,并且会逐步引导您在 Android Studio 中完成 Forage 应用项目,包括项目设置和测试。

前提条件

  • 此项目适合已学完《使用 Kotlin 进行 Android 开发的基础知识》课程第 5 单元的学生。

构建内容

  • 通过实现一个实体、DAO、ViewModel 和数据库类,借助 Room 为现有应用增加数据持久性功能。

所需条件

  • 一台安装了 Android Studio 的计算机。

2. 已完成的应用概览

完成后的 Forage 应用可让用户跟踪他们在自然界中搜寻到的物品,例如食物。在使用 Room 的情况下,此数据可在会话间持久保留。您要运用您掌握的 Room 知识以及对数据库执行读取、写入、更新和删除操作的知识,在 Forage 应用中实现数据持久性。下面介绍了完成后的应用及其功能。

用户首次启动该应用时,系统会显示一个空屏幕,其中包含一个 Recycler 视图(用于显示搜寻的物品)和右下角的一个悬浮按钮(用于添加新物品)。

3edd87e63c387d88.png

添加新物品时,用户可以指定名称、找到物品的位置以及一些其他备注。此外还有一个复选框,用于表明食物目前是否应季。

6c0c739569bb3b4f.png

物品添加后将显示在第一个屏幕的 Recycler 视图中。

bcc75e60b70320e8.png

点按物品后将转到一个详情屏幕,显示相应物品的名称、位置和备注。

5096995a4921dcac.png

悬浮按钮也会从加号变为修改图标。点按此按钮将转到另一个屏幕,供您修改名称、位置、备注和“应季”复选框。点按删除按钮会从数据库中移除该物品。

f8c708fed3dede1a.png

虽然此应用的界面部分已实现,但您的任务是运用您掌握的 Room 知识实现数据持久性,让应用可以对数据库执行读取、写入、更新和删除物品的操作。

3. 开始

下载项目代码

请注意,文件夹名称为 android-basics-kotlin-forage-app。在 Android Studio 中打开项目时,请选择此文件夹。

如需获取此 Codelab 的代码并在 Android Studio 中打开它,请执行以下操作。

获取代码

  1. 点击提供的网址。此时,项目的 GitHub 页面会在浏览器中打开。
  2. 在项目的 GitHub 页面上,点击 Code 按钮,以打开一个对话框。

5b0a76c50478a73f.png

  1. 在对话框中,点击 Download ZIP 按钮,将项目保存到计算机上。等待下载完成。
  2. 在计算机上找到该文件(可能在 Downloads 文件夹中)。
  3. 双击 ZIP 文件进行解压缩。系统将创建一个包含项目文件的新文件夹。

在 Android Studio 中打开项目

  1. 启动 Android Studio。
  2. Welcome to Android Studio 窗口中,点击 Open an existing Android Studio project

36cc44fcf0f89a1d.png

注意:如果 Android Studio 已经打开,请依次选择 File > New > Import Project 菜单选项。

21f3eec988dcfbe9.png

  1. Import Project 对话框中,转到解压缩的项目文件夹所在的位置(可能在 Downloads 文件夹中)。
  2. 双击该项目文件夹。
  3. 等待 Android Studio 打开项目。
  4. 点击 Run 按钮 11c34fc5e516fb1c.png 以构建并运行应用。请确保该应用可以正常使用。
  5. Project 工具窗口中浏览项目文件,了解应用的设置方式。

4. 设置项目以使用 Room

定义 Forableable 实体

该项目已有一个用于定义应用数据的 Forageable 类 (model.Forageable.kt)。此类具有以下几个属性:idnameaddressinSeasonnotes

data class Forageable(
   val id: Long = 0,
   val name: String,
   val address: String,
   val inSeason: Boolean,
   val notes: String?
)

不过,若要使用此类来存储持久性数据,您需要将其转换为 Room 实体。

  1. 使用 @Entity 和表名称 "forageable_database" 为此类添加注解。
  2. id 属性设为主键。系统应该会自动生成主键。
  3. inSeason 属性的列名称设为 "in_season"

实现 DAO

您或许已猜到,如果要对您将从视图模型访问的数据库执行读写操作,您要在 ForageableDao (data.ForageableDao.kt) 中定义用于执行读写操作的方法。由于 DAO 只是您定义的接口,因此您实际上并不需要编写任何代码来实现这些方法,而是应该使用 Room 注解,并根据需要指定 SQL 查询。

ForageableDao 接口中,您需要添加五个方法。

  1. getForageables() 方法,用于返回数据库中所有行的 Flow<List<Forageable>>
  2. getForageable(id: Long) 方法,用于返回与指定 id 匹配的 Flow<Forageable>
  3. insert(forageable: Forageable) 方法,用于将一个新的 Forageable 插入到数据库中。
  4. update(forageable: Forageable) 方法,接受一个现有 Forageable 作为参数并更新相应的行。
  5. delete(forageable: Forageable) 方法,接受一个 Forageable 作为参数并从数据库中将其删除。

实现视图模型

ForageableViewModel (ui.viewmodel.ForageableViewModel.kt) 已部分实现,但您需要添加用于访问 DAO 方法的功能,使其可实际读取和写入数据。请执行以下步骤来实现 ForageableViewModel

  1. 应将一个 ForageableDao 实例作为参数传入类构造函数。
  2. 创建一个类型为 LiveData<List<Forageable>> 的变量,该变量使用 DAO 获取完整的 Forageable 实体列表,并将结果转换为 LiveData
  3. 创建一个接受 ID(类型为 Long)作为参数的方法,该方法通过对 DAO 调用 getForageable() 方法返回 LiveData<Forageable>,并将结果转换为 LiveData
  4. addForageable() 方法中,使用 viewModelScope 启动协程,并使用 DAO 将 Forageable 实例插入到数据库中。
  5. updateForageable() 方法中,使用 DAO 更新 Forageable 实体。
  6. deleteForageable() 方法中,使用 DAO 更新 Forageable 实体。
  7. 创建一个 ViewModelFactory,它可使用 ForageableDao 构造函数参数创建一个 ForageableViewModel 实例。

实现数据库类

ForageDatabase (data.ForageDatabase.kt) 类是将实体和 DAO 实际公开给 Room 的类。请按照说明实现 ForageDatabase 类。

  1. 实体:Forageable
  2. 版本:1
  3. exportSchema:false
  4. ForageDatabase 类中,添加一个抽象函数以返回 ForageableDao
  5. ForageDatabase 类中,使用名为 INSTANCE 的私有变量和返回 ForageDatabase 单例的 getDatabase() 函数定义一个伴生对象。
  1. BaseApplication 类中,创建一个 database 属性,返回使用延迟初始化的 ForageDatabase 实例。

5. 在 fragment 中持久保留并从中读取数据

设置实体、DAO、视图模型并定义数据库类以将其公开给 Room 后,您只需修改 fragment 便可访问视图模型。您需要在三个文件(应用中的每个屏幕对应一个文件)中进行更改。

Forageable 列表

forageable 列表只需要两项内容:对视图模型的引用以及对完整 forageable 列表的访问。请在 ui.ForageableListFragment.kt 中执行以下任务。

  1. 该类已有 viewModel 属性。但是,这样使用的并不是您在上一步中定义的工厂。您需要先重构此声明才能使用 ForageableViewModelFactory
private val viewModel: ForageableViewModel by activityViewModels {
   ForageableViewModelFactory(
       (activity?.application as BaseApplication).database.foragableDao()
   )
}
  1. 然后,在 onViewCreated() 中,观察 viewModel 中的 allForageables 属性,并酌情对适配器调用 submitList() 以填充列表。

Forageable 详情屏幕

ui/ForageableDetailFragment.kt 中的详情列表执行的操作几乎相同。

  1. 转换 viewModel 属性以正确初始化 ForageableViewModelFactory
  2. onViewCreated() 中,对视图模型调用 getForageable() 并传入 id 以获取 Forageable 实体。观察 LiveData 并将结果设为 forageable 属性,然后调用 bindForageable() 以更新界面。

添加和修改 forageable 屏幕

最后,您需要在 ui.AddForageableFragment.kt 中执行类似的操作。请注意,此屏幕还负责更新和删除实体。不过,视图模型中的这些方法已在正确的位置调用。您只需要在此文件中进行两项更改。

  1. 同样,重构 viewModel 属性以使用 ForageableViewModelFactory
  2. onViewCreated() 中的 if 语句块中设置删除按钮的可见性之前,对视图模型调用 getForageable(),传入 id,并将结果设为 forageable 属性。

在 fragment 中需要执行的操作就这些。现在,您可以运行自己的应用,应该能看到所有持久性功能都在实际发挥作用。

6. 测试说明

运行测试

如需运行测试,您可以执行以下任一操作。

对于单个测试用例,打开测试用例类 PersistenceInstrumentationTests.kt,并点击类声明左侧的绿色箭头。然后,从菜单中选择“Run”选项。这样会运行测试用例中的所有测试。

3e640ec727599a6d.png

经常会有您只想运行单个测试的情况,例如,有一个测试失败,而其他测试都通过。您可以像运行整个测试用例一样运行单个测试。点击绿色箭头并选择 Run 选项。

8647a76419540499.png

如果您有多个测试用例,也可以运行整个测试套件。就像运行应用一样,您可以在 Run 菜单中找到此选项。

7a925c5e196725bb.png

请注意,Android Studio 默认运行您运行的最后一个目标(应用目标、测试目标等),因此如果菜单仍然显示 Run > Run ‘app',您可以通过选择 Run > Run 运行测试目标。

90d3ec5ca5928b2a.png

然后从弹出式菜单中选择测试目标。

3b1a7d82a55b5f13.png