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-51

2、更新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,它提供以下查询:

  1. 按字母顺序排列所有标题
  2. 插入一个Todo
  3. 删除所有Todo
@Dao
interface TodoDao {
    @Query("SELECT * from todo_database ORDER BY todo_title ASC")
    fun getAlphabetizedTodoList(): LiveData>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(todo: Todo)

    @Query("DELETE FROM todo_database")
    suspend fun deleteAll()
}

让我们来看一下:

  1. TodoDao是一个接口;Dao必须是接口或者抽象类。
  2. @Dao注解表示它是一个Room的Dao类
  3. suspend fun insert(todo: Todo)声明一个暂停功能以插入一个Todo。
  4. 该@Insert注释是一种特殊的DAO方法注释,您无需提供任何SQL!(还有@Delete和@Update注释,用于删除和更新行,但未在此应用中使用它们。)
  5. onConflict = OnConflictStrategy.IGNORE如果冲突策略中选定的Todo与列表中已有的Todo完全相同,则会忽略该单词。
  6. 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>

在本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。
  • 我们定义了singletonTodoRoomDatabase,以防止同时打开数据库的多个实例。
  • 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> = todoDao.getAlphabetizedTodoList()

    suspend fun insert(todo: Todo){
        todoDao.insert(todo)
    }
}

主要代码:

  1. DAO被传递到存储库构造函数,而不是整个数据库。这是因为它只需要访问DAO,因为DAO包含数据库的所有读/写方法。无需将整个数据库公开到存储库。
  2. Todo列表是公共财产。通过LiveDataRoom获取单词列表进行初始化;之所以可以这样做,是因为我们定义了getAlphabetizedTodoList返回LiveData的方法。Room在单独的线程上执行所有查询。然后,当LiveData数据已更改时,observed 将在主线程上通知观察者。
  3. suspend修饰符告诉编译器,这需要从协同程序或其他暂停功能调用。

9、创建ViewModel

什么是VIewModel?

ViewModel的作用是提供数据的UI和生存的配置更改。ViewModel充当存储库和UI之间的通信中心。您还可以使用ViewModel在片段之间共享数据。ViewModel是生命周期库的一部分。

为什么要使用ViewModel

ViewModel以对生命周期敏感的方式保存应用程序的UI数据,以在配置更改后生存下来。将应用程序的UI数据Activity类与Fragment类分开,可以更好地遵循单一职责原则:您的活动和片段负责将数据绘制到屏幕上,而您则ViewModel可以负责保存和处理UI所需的所有数据。
在中ViewModelLiveData用于UI将使用或显示的可变数据。使用LiveData有几个好处:

  1. 您可以将观察者放在数据上(而不是轮询更改),并且仅
    在数据实际更改时才更新UI。
  2. 资源库和用户界面由完全分隔.
  3. 没有来自的数据库调用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>

    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连接。

Github仓库

你可能感兴趣的:(Android Room 的Kotlin实现)