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<List<Todo>>

    @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<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。
  • 我们定义了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<List<Todo>> = 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<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连接。

Github仓库

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容