Android Room 的Kotlin实现
原文链接https://www.shanya.world/archives/e6cb5eee.html
Demo简介
本Demo是演示Room在Kotlin语法下使用的一个简单的应用程序。
Room介绍
Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。
该库可帮助您在运行应用的设备上创建本地数据库。
步骤
1、新建一个空的工程
Android-Room-By-Kotlin2020-1-27-19-15-512、更新Gradle文件
必须将组件库添加到Gradle文件中
在你的build.gradle
(Module:app)中进行以下更改:
通过将kapt 注释处理器 Kotlin插件添加到(Module:app)文件顶部定义的其他插件之后,来应用它。
bulid.gradle
apply plugin: 'kotlin-kapt'
android {
// 系统自动生成的在这里省略了……
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
}
}
在代码dependencies
块的末尾添加以下代码。
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
implementation "androidx.room:room-ktx:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
androidTestImplementation "androidx.arch.core:core-testing:$rootProject.androidxArchVersion"
// ViewModel Kotlin support
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"
// Coroutines
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"
// UI
implementation "com.google.android.material:material:$rootProject.materialVersion"
// Testing
androidTestImplementation "androidx.arch.core:core-testing:$rootProject.coreTestingVersion"
之后再在 项目的build.gradle
(Project)
ext {
roomVersion = '2.2.1'
archLifecycleVersion = '2.2.0-rc02'
androidxArchVersion = '2.1.0'
coreTestingVersion = "2.1.0"
coroutines = '1.3.2'
materialVersion = "1.0.0"
}
这里也可以去官网找最新的版本号 链接
3、创建一个实体
Room允许您通过Entity创建表。让我们现在开始。
@Entity(tableName = "todo_database")
data class Todo (
@PrimaryKey(autoGenerate = true) val id:Int,
@ColumnInfo(name = "todo_title") val title:String,
@ColumnInfo(name = "todo_content") val content:String
)
分析以下上述代码中@注释的意思
-
@Entity(tableName = "todo_database")
每一个@Entity
类代表一个SQLite表。注释你的类以表明它是一个实体类。如果希望表名与类名不同,则可以指定表名。这个表名为“todo_database” -
@PrimaryKey
每一个实体都需要一个主键。这里使用autoGenerate = true
来自动生成 -
@ColumnInfo(name = "todo_title")
如果您希望表中的列名称与成员变量的名称不同,则指定该名称。这将列命名为“todo_title”。
4、创建Dao
什么是Dao?
在DAO(数据访问对象)中,指定SQL查询并将它们与方法调用关联。编译器检查SQL并从便利注释中生成常见查询(例如)的查询@Insert。Room使用DAO为您的代码创建一个干净的API。
DAO必须是接口或抽象类。
默认情况下,所有查询必须在单独的线程上执行。
Room具有协程支持,允许您的查询使用suspend修饰符注释,然后从协程或另一个暂停函数调用。
编写Dao
让我们编写一个DAO,它提供以下查询:
- 按字母顺序排列所有标题
- 插入一个Todo
- 删除所有Todo
@Dao
interface TodoDao {
@Query("SELECT * from todo_database ORDER BY todo_title ASC")
fun getAlphabetizedTodoList(): LiveData<List<Todo>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(todo: Todo)
@Query("DELETE FROM todo_database")
suspend fun deleteAll()
}
让我们来看一下:
-
TodoDao
是一个接口;Dao必须是接口或者抽象类。 - 该
@Dao
注解表示它是一个Room的Dao类 -
suspend fun insert(todo: Todo)
声明一个暂停功能以插入一个Todo。 - 该@Insert注释是一种特殊的DAO方法注释,您无需提供任何SQL!(还有@Delete和@Update注释,用于删除和更新行,但未在此应用中使用它们。)
-
onConflict = OnConflictStrategy.IGNORE
如果冲突策略中选定的Todo与列表中已有的Todo完全相同,则会忽略该单词。 -
suspend fun deleteAll()
声明一个暂停功能以删除所有Todo。
- 关于
@Query
的用法有点多且杂查询请自行查阅资料
5、LiveData类
数据更改时,通常需要采取一些措施,例如在UI中显示更新的数据。这意味着您必须观察数据,以便在数据更改时可以做出反应。
根据数据的存储方式,这可能很棘手。观察应用程序多个组件之间的数据更改可以在组件之间创建明确的,严格的依赖路径。这使测试和调试变得非常困难。
LiveData,用于数据观察的生命周期库类可解决此问题。LiveData在方法描述中使用类型的返回值,然后Room会生成所有必要的代码来更新LiveData数据库。
上面的代码块中已有体现,如下
@Query("SELECT * from todo_database ORDER BY todo_title ASC")
fun getAlphabetizedTodoList(): LiveData<List<Todo>>
在本Demo的后面,将通过Observer 跟踪数据更改。
7、添加RoomDatabase
什么是RoomDatabase?
- Room是SQLite数据库之上的数据库层。
- Room负责处理以前使用NET处理的普通任务
- Room使用DAO向其数据库发出查询。
- 默认情况下,为避免UI性能下降,Room不允许您在主线程上发出查询。当Room查询返回LiveData时,查询将自动在后台线程上异步运行。
- Room提供了SQLite语句的编译时检查。
编写RomDatabase
RoomDatabase类必须是抽象的并且可以扩展RoomDatabase。通常,整个应用程序只需要一个Room数据库实例。
让我们现在做一个。创建一个名为的Kotlin类文件,WordRoomDatabase并添加以下代码:
@Database(entities = arrayOf(Todo::class),version = 1,exportSchema = false)
abstract class TodoDatabase: RoomDatabase() {
abstract fun todoDao(): TodoDao
private class TodoDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
var todoDao = database.todoDao()
// Delete all content here.
todoDao.deleteAll()
// Add sample todos.
var todo = Todo(0,"title","content")
todoDao.insert(todo)
todo = Todo(0,"title1","content1")
todoDao.insert(todo)
// TODO: Add your own words!
todo = Todo(0,"title2","content2")
todoDao.insert(todo)
}
}
}
}
companion object{
@Volatile
private var INSTANCE: TodoDatabase? = null
fun getDatabase(context: Context,scope: CoroutineScope): TodoDatabase{
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
TodoDatabase::class.java,
"todo_database"
).addCallback(TodoDatabaseCallback(scope)).build()
INSTANCE = instance
return instance
}
}
}
}
让我们来看一下代码:
-
Room
的数据库类必须是abstract
并且扩展RoomDatabase
- 您使用注释该类为Room数据库,
@Database
并使用注释参数声明属于数据库的实体并设置版本号。每个实体对应一个将在数据库中创建的表。数据库迁移不在此代码实验室的范围内,因此exportSchema
在此处设置为false
以避免生成警告。在实际的应用程序中,您应该考虑为Room设置目录以用于导出架构,以便可以将当前架构签入版本控制系统。 - 您通过为每个
@Dao
创建一个抽象的“getter”方法来使数据库提供其DAO。 - 我们定义了
singleton
,TodoRoomDatabase
,以防止同时打开数据库的多个实例。 -
getDatabase
返回单例。它将在首次访问数据库时使用Room的数据库构建器RoomDatabase
在类的应用程序上下文中创建一个对象TodoRoomDatabase
并将其命名,从而创建数据库"todo_database"。
8、创建Repository
什么是Repository?
存储库类抽象了对多个数据源的访问。该存储库不是体系结构组件库的一部分,而是建议的代码分离和体系结构最佳实践。Repository类提供了一个干净的API,用于对应用程序其余部分的数据访问。
Android-Room-By-Kotlin2020-1-28-9-8-56编写Repository
创建一个名为的Kotlin类文件TodoRepository
,并将以下代码粘贴到其中:
class TodoRepository(private val todoDao: TodoDao) {
val allTodo: LiveData<List<Todo>> = todoDao.getAlphabetizedTodoList()
suspend fun insert(todo: Todo){
todoDao.insert(todo)
}
}
主要代码:
- DAO被传递到存储库构造函数,而不是整个数据库。这是因为它只需要访问DAO,因为DAO包含数据库的所有读/写方法。无需将整个数据库公开到存储库。
-
Todo
列表是公共财产。通过LiveData
从Room
获取单词列表进行初始化;之所以可以这样做,是因为我们定义了getAlphabetizedTodoList
返回LiveData
的方法。Room
在单独的线程上执行所有查询。然后,当LiveData
数据已更改时,observed
将在主线程上通知观察者。 - 该
suspend
修饰符告诉编译器,这需要从协同程序或其他暂停功能调用。
9、创建ViewModel
什么是VIewModel?
ViewModel
的作用是提供数据的UI和生存的配置更改。ViewModel
充当存储库和UI之间的通信中心。您还可以使用ViewModel
在片段之间共享数据。ViewModel
是生命周期库的一部分。
为什么要使用ViewModel
ViewModel
以对生命周期敏感的方式保存应用程序的UI数据,以在配置更改后生存下来。将应用程序的UI数据Activity
类与Fragment
类分开,可以更好地遵循单一职责原则:您的活动和片段负责将数据绘制到屏幕上,而您则ViewModel
可以负责保存和处理UI所需的所有数据。
在中ViewModel
,LiveData
用于UI将使用或显示的可变数据。使用LiveData有几个好处:
- 您可以将观察者放在数据上(而不是轮询更改),并且仅
在数据实际更改时才更新UI。 - 资源库和用户界面由完全分隔.
- 没有来自的数据库调用
ViewModel
(这全部在存储库中处理),使代码更具可测试性。
viewModelScope
在Kotlin,所有协程都在内运行CoroutineScope
。示波器通过其工作控制协程的生命周期。取消合并范围的作业时,它将取消在该合并范围内启动的所有协程。
AndroidX lifecycle-viewmodel-ktx
库添加了viewModelScope
类的扩展功能ViewModel
,使您能够使用范围。
实现ViewModel
为此创建一个Kotlin类文件,并添加以下代码:
class TodoViewModel(application: Application): AndroidViewModel(application) {
private val repository: TodoRepository
val allTodo: LiveData<List<Todo>>
init {
val wordsDao = TodoDatabase.getDatabase(application,viewModelScope).todoDao()
repository = TodoRepository(wordsDao)
allTodo = repository.allTodo
}
fun insert(todo: Todo) = viewModelScope.launch {
repository.insert(todo)
}
}
10、最后
现在我们只要调用ViewModel里面的inset方法即可插入数据了。
具体的测试界面(RecyclerView等)代码见下方Github连接。