项目档案

房间数据库示例

在我们过去的一些示例中,我们通过将赢博体育程序数据转换为JSON来本地存储赢博体育程序数据,然后将JSON代码存储在本地文本文件中。对于简单的示例,该系统还可以,但一旦赢博体育程序数据变得更复杂,该系统就会崩溃。

更强大的本地数据存储机制是使用数据库来存储赢博体育程序的数据。在这些课堂笔记中,我将向您介绍Android的Room数据库系统,它将使我们能够将赢博体育程序数据存储在本地数据库中。

我将用来演示Room的示例赢博体育程序是一个成绩簿赢博体育程序,它允许我们存储一组学生在多个测验中的测验成绩。

这款赢博体育使用了一个带有三个标签的标签式界面。第一个选项卡允许我们创建学生。

第二个选项卡允许我们创建测验

第三个屏幕允许我们输入和查看学生在测验中的分数。有一个学生和测验的下拉菜单,我们可以使用它来为特定的学生拉出测验。然后,我们可以查看该学生的测验分数,如果我们选择,可以输入一个新的分数。

准备使用房间

在项目中使用Room的第一步是为项目添加一些依赖项:

implementation("com.google.dagger:dagger-compiler:2.51.1") ksp("com.google.dagger:dagger-compiler:2.51.1") val room_version = "2.6.1" implementation("androidx.room: Room -runtime:$room_version") annotationProcessor("androidx.room: Room -compiler:$room_version") ksp("androidx.room: Room -compiler:$room_version") //可选- Kotlin扩展和协同程序支持Room实现("androidx.room: Room -ktx:$room_version")

设置实体、dao和数据库

Room是一个对象-关系数据库系统。这意味着我们将使用Room在关系数据库系统中存储特殊构造的对象。

为了设置将被存储在数据库中的对象,我们做了一系列@Entity声明:

@实体数据类学生(@PrimaryKey(autoGenerate=true) val id: Int = 0, val firstName:字符串,val lastName:字符串)@实体数据类Quiz(@PrimaryKey(autoGenerate=true) val id: Int = 0, val maxPoints: Int, val name:字符串)@实体数据类Grade(@PrimaryKey(autoGenerate=true) val id: Int = 0, val Student: Int, val Quiz: Int, val Grade: Int)

首先,这些声明是我们之前看到的类型的一组数据类声明。我们用一些注释补充了这些类声明。有@Entity注释,它将类标记为Room实体,还有@PrimaryKey注释,我们将其赢博体育于每个对象的一个字段。在本例中,我们还指定在创建每个对象并将其插入数据库时,Room应该自动为我们生成这些id号。

接下来,我们设置一个数据访问对象(dao)。这是一个特殊的接口,包含我们计划在赢博体育程序中进行的每个不同数据库交互的方法。

@Dao接口GradesDao {@Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertStudent(student: student) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertQuiz(quiz: quiz) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertGrade(grade: grade) @Delete suspend fun removeGrade(grade: grade) @Query("SELECT * from student ORDER BY lastName ASC, firstName ASC") suspend fun loadStudents():List<Student b> @Query("SELECT * from quiz") suspend fun loadQuizzes(): List< quiz > @Query("SELECT * from grade WHERE Student =: Student and quiz =:quiz") suspend fun getGrade(Student: Int, quiz: Int): List< grade >}

这里最重要的方法是带有@Query注释的方法。这些注释中的每一个都使用了一些类似sql的代码,这些代码精确地指定了每个查询的功能。

关于这些注释的更多信息可以在在线文档中找到。

我们需要的最后一部分是数据库本身的声明:

@Database(entities = [Student::class,Quiz::class,Grade::class], version = 1)抽象类GradesDatabase: RoomDatabase(){抽象趣味gradesDao(): gradesDao}

设置视图模型

同样,这个赢博体育程序将使用视图模型类。在前面的示例中,我们使用视图模型作为到后端服务器的网关。对于本例,我们的视图模型将充当到Room数据库的网关。

下面是我们的视图模型类的代码:

class GradesModel(application: application): AndroidViewModel(application) {lateinit var dao: GradesDao val students = mutableStateOf<List<Student>?>(null) val firstStudent = mutableStateOf<Student?>(null) val quizzes = mutableStateOf<List<Quiz>?>(null) val firstQuiz = mutableStateOf<Quiz?>(null) val grade = mutableStateOf< grade ?>(null) init {val db = Room。databaseBuilder(application, GradesDatabase::class.java, "grades").build() dao = db.gradesDao() viewModelScope。launch {var studentList: List<Student> withContext(Dispatchers.IO) {studentList = dao.loadStudents()} withContext(Dispatchers.Main) {if(!studentList. isempty ()) firstStudent。value = studentList[0]学生。value = studentList} var quizList: List<Quiz> withContext(Dispatchers.IO) {quizList = dao.loadQuizzes()} withContext(Dispatchers.Main) {if(!quizList. isempty ()) firstQuiz。value = quizList[0] quizzes。value = quizList}}} fun addStudent(student: student) {viewModelScope。launch {var studentList: List<Student> withContext(Dispatchers.IO) {dao.insertStudent(Student) studentList = dao.loadStudents()} withContext(Dispatchers.Main) {firstStudent. iovalue = studentList[0]学生。value = studentList}}} fun addQuiz(quiz: quiz) {viewModelScope。启动{var quizList: List<Quiz> withContext(Dispatchers.IO) {dao.insertQuiz(Quiz) quizList = dao.loadQuizzes()} withContext(Dispatchers.Main) {firstQuiz. iovalue = quizList[0] quizzes。value = quizList}}} fun initGrade() {if(firstStudent。= null && firstQuiz. value != null) getGrade(firstStudent.value!!,firstQuiz.value!!)addGrade(newGrade: Grade) {viewModelScope。launch {var grades: List<Grade> withContext(Dispatchers.IO) {grades = dao. iogetGrade(student = newGrade)。student, quiz = newGrade。如果(!grades. isempty ()) dao.removeGrade(grades[0]) dao.insertGrade(newGrade)} withContext(Dispatchers.Main) {grade。value = newGrade}}} fun getGrade(student: student,quiz: quiz) {viewModelScope。launch {var grades: List<Grade> withContext(Dispatchers.IO) {grades = dao. iogetGrade(学生=学生。Id, quiz = quiz。id)} withContext(Dispatchers.Main) {if (!grades.isEmpty()) {Value = grades[0]} else {grade。Value = null}}}}}

这里有三点需要注意:

  1. 顶部列出的成员变量是我们将从数据库中提取的各种对象的本地存储。这里有一个学生列表、一个测验列表和一个Grade对象,用于我们当前正在处理的年级。
  2. init代码通过实例化dao对象建立到数据库的连接,然后加载一些初始数据供我们使用。
  3. 其余的方法设置我们计划执行的赢博体育数据库交互。这些方法的目的都是更新数据库中的数据,或者从数据库中提取一些特定的数据以存储在成员变量中。

让我们仔细看看这里的一个方法。

addStudent(student: student) {viewModelScope。launch {var studentList: List<Student> withContext(Dispatchers.IO) {dao.insertStudent(Student) studentList = dao.loadStudents()} withContext(Dispatchers.Main) {firstStudent. iovalue = studentList[0]学生。value = studentList}}

在这些方法中,我们需要处理的第一件事是,我们与数据库的赢博体育交互都将以异步方式完成。这将要求我们设置协同程序,就像我们在互联网上与服务器通信时所做的一样。正如我们在前面的示例中看到的,数据库交互将在IO线程组上运行,而更新成员变量的任何代码都必须在主线程组上运行。

执行数据库交互本身非常简单—我们只需要为每个交互调用适当的dao方法。

赢博体育程序的其余部分

现在我们已经建立并运行了一个视图模型,赢博体育程序的其余部分看起来与我们看到的前面的示例非常相似。用户界面的完整代码在MainActivity中。像往常一样。

用户界面代码中只有一个方面是我们在前面的示例中没有涉及的。第三个屏幕将显示学生和测验的下拉菜单,使用我没有在任何以前的例子中介绍的组件,材料库的ExposedDropDownMenuBox。使用该组件的主要优点是,它可以更好地简化组件的TextField和ExposedDropdownMenu部分的设置。除此之外,该组件设置菜单的方式与我在前面的示例中展示的方式大致相同。