【Jetpack】Room + ViewModel + LiveData 综合使用 ( 核心要点说明 | 组合方式 | 代码示例 )

news2024/11/26 3:33:19

文章目录

  • 一、Room + ViewModel + LiveData 框架使用核心要点
    • 1、Room 框架优化分析
    • 2、Google 官方建议的 Room + ViewModel + LiveData 架构
    • 3、Room 与 LiveData 结合使用要点
    • 4、Repository 层核心要点
    • 5、ViewModel + Room 结合使用
    • 6、Activity 组件中 ViewModel 使用要点
    • 7、Room 框架主动查询数据库数据需保留除 LiveData 返回值外的正常查询方法
  • 二、完整代码示例
    • 1、build.gradle 构建脚本
    • 2、Room 框架相关代码
      • Entity 实体类
      • Dao 数据库访问接口对象
      • Database 数据库实体类
    • 3、Repository 代码
    • 4、ViewModel 代码
    • 5、Activity 组件中的最终调用代码
    • 6、执行结果





一、Room + ViewModel + LiveData 框架使用核心要点




1、Room 框架优化分析


在上一篇博客 【Jetpack】使用 Room 框架访问 Android 平台 SQLite 数据库 ( 导入依赖 | 定义 Entity 实体类 | 定义 Dao 数据库访问对象接口 | 定义数据库实例类 ) 中 , 实现了 使用 Room 框架访问 Android 中的 SQLite 数据库的操作 , 每当数据库中的数据发生变化时 , 就需要开启线程 , 重新获取数据库中的数据 ;

为了优化上述问题 , 可以引入 LiveData ViewModel ,

  • ViewModel 是 视图 View数据模型 Model 之间 数据交互的 桥梁 ;
  • LiveData 是基于 ViewModel 的 , 是 对 ViewModel 数据维护的一个补充 ;

在 ViewModel 中使用了 LiveData 后 ,

先调用 LiveData#observe 函数 为 LiveData 设置 androidx.lifecycle.Observer 监听器 ,

如果 该监听器 监听到了 LiveData 数据变化 ,

直接 回调 androidx.lifecycle.Observer 监听器 的 androidx.lifecycle.Observer#onChanged 函数 ,

最终在上述回调函数中执行 查询数据库 和 更新视图 操作 ;


2、Google 官方建议的 Room + ViewModel + LiveData 架构


下图是 Google 官方 提出的 Room + ViewModel + LiveData 架构设计 建议 :

在这里插入图片描述
下面分析上述 架构图 中的 架构分层 ;


Model 数据模型层 :

  • 本地数据访问 : 使用 Room 框架 访问本地的 SQLite 数据库 ;
  • 远程数据访问 : 使用 Retrofit 框架 访问 远程数据源 ( Remote Data Source ) ;

Repository 层 : 该层负责 封装 Model 数据模型层 , 用于与 ViewModel 层进行交互 ;

ViewModel 视图模型层 : 该层 不与 Room 和 Retrofit 直接交互 , 而是与 Repository 层 进行交互 ; 在 ViewModel 层引入 LiveData 监听数据变化 , 如果数据发生变化则在 LiveData 设置的 androidx.lifecycle.Observer 监听器回调中 更新 View 视图 ;

View 视图层 : Activity / Fragment 负责视图显示的 系统组件 , 负责维护 Android 视图组件 , 显示的数据由 ViewModel 提供 ;


3、Room 与 LiveData 结合使用要点


对于 Room 框架使用来说 ,

  • Room 与 LiveData 结合使用 ,
  • Room 单独使用 ,

唯一的区别是 Room 框架中的 Dao 数据访问接口对象 中的 查询方法 , 其返回值类型改为 LiveData 类型 , LiveData 的泛型为 原来的查询方法的返回值类型 ;

Dao 查询方法的返回值由 List<Student> 变为 LiveData<List<Student>> ;


Room 框架中 , Entity 实体类 , Database 数据库实体类 , 定义方式保持不变 ,

  • Entity 实体类 使用 @Entity 注解修饰 , 并使用 @PrimaryKey 注解修饰主键 , 使用 @ColumnInfo 注解 修饰普通字段 , 使用 @Ignore 注解 修饰不需要的字段或方法 ;
  • Database 数据库实体类 使用 @Database 注解修饰该类 , 其中定义 获取 Dao 数据库访问对象的抽象方法 , 以及 将该抽象类设置成 单例类 , 在单例对象初始化时创建数据库 ;

Room 框架中的 Dao 数据库访问对象接口 的定义方式需要作出改变 , 涉及到数据库查询的 接口方法时 , 其返回值需要 返回 LiveData 类型 , 泛型设置为 List<Student> 类型 ;

    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun query(): LiveData<List<Student>>

    /**
     * 根据传入的 id 查询数据库表
     * 在注解中使用 :id 调用参数中的 id: Int
     */
    @Query("select * from student where id = :id")
    fun query(id: Int): LiveData<List<Student>>

原来的方法如下 , 其查询接口的返回值是 List<Student> ;

    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun query(): List<Student>

    /**
     * 根据传入的 id 查询数据库表
     * 在注解中使用 :id 调用参数中的 id: Int
     */
    @Query("select * from student where id = :id")
    fun query(id: Int): List<Student>

Room 框架的用法 , 参考 【Jetpack】使用 Room 框架访问 Android 平台 SQLite 数据库 ( 导入依赖 | 定义 Entity 实体类 | 定义 Dao 数据库访问对象接口 | 定义数据库实例类 ) 博客 ;


4、Repository 层核心要点


Repository 层负责 封装 Model 数据模型层 , 用于与 ViewModel 层进行交互 ;

因此在 Repository 中 , 需要 持有 Dao 数据访问接口对象 ;

	lateinit var dao: StudentDao

Dao 又是通过 Database 得到的 ,

因此在 该 Repository 中需要先获取 Database 数据库实例类对象 , 然后通过 Database 获取 Dao 数据访问接口 ;

    constructor(context: Context) {
        var database = StudentDatabase.inst(context)
        this.dao = database.studentDao()
    }

此外 , 还需要 在 Repository 层中 , 维护数据库的 增删改查 方法 , 该操作直接调用 Dao 数据库访问接口对象完成 ,

    fun insert(student: Student) {
        this.dao.insert(student)
    }

    fun query(): LiveData<List<Student>> {
        return this.dao.query()
    }

    fun update(student: Student) {
        this.dao.update(student)
    }

    fun delete(id: Int) {
        var student = Student(id)
        this.dao.delete(student)
    }

特别注意 , 为了 将 Room 与 LiveData 结合 , Dao 查询方法的返回值是 LiveData 类型 ;

    fun query(): LiveData<List<Student>> {
        return this.dao.query()
    }

5、ViewModel + Room 结合使用


根据 Google 官方的架构建议 , ViewModel 不与 Room 直接交互 , 而是由 Repository 将 Room 封装起来 , 由 ViewModel 与 Repository 进行交互 ;

ViewModel 与 Room 结合使用 , 实际上与 Repository 进行交互 ;

ViewModel 需要继承 AndroidViewModel , 并且需要在类中维护 Repository 成员变量 ,

class ViewModel: AndroidViewModel {

    lateinit var repository: Repository

    constructor(application: Application) : super(application) {
        this.repository = Repository(application)
    }

同时 , 需要 在 ViewModel 中维护 数据库 的 增删改查 的对应函数 , 通过调用 Repository 成员边来那个实现对数据库的操作 , 查询函数 的返回值是 LiveData 类型的 ;

    fun insert(student: Student) {
        this.dao.insert(student)
    }

    fun query(): LiveData<List<Student>> {
        return this.dao.query()
    }

    fun update(student: Student) {
        this.dao.update(student)
    }

    fun delete(id: Int) {
        var student = Student(id)
        this.dao.delete(student)
    }

6、Activity 组件中 ViewModel 使用要点


在 Activity 组件中 , 通过调用 ViewModel 视图模型获取 数据库中的数据 , ViewModel 调用 Repository 层的增删改查方法 , Repository 调用 Room 框架的相关方法操作 SQLite 数据库 ;

首先 , 获取 ViewModel 视图模型 ;

        // 获取 ViewModel 视图模型对象
        var viewModel: ViewModel = ViewModelProvider(
            this,
            AndroidViewModelFactory(application)).get(ViewModel::class.java)

然后 , 为 ViewModel 视图模型中获取的 LiveData 数据设置 Observer 监听 ;

        // 为 ViewModel 中获取的 LiveData 数据设置 Observer 监听
        viewModel.query().observe(this, object: Observer<List<Student>> {
            override fun onChanged(t: List<Student>?) {
                Log.i("MainActivity", "Observer#onChanged 回调, List<Student>: " + t)
            }
        })

最后 , 通过调用 ViewModel 中定义的 数据库操作 方法 , 修改数据库中的数据 , 如果数据库中的数据发生了改变 , 就会自动回调 Observer#onChanged 方法 ;

thread(start = true) {

            Thread.sleep(500)

            // 插入数据
            var s1 = Student("Tom", 18)
            var s2 = Student("Jerry", 16)

            viewModel.insert(s1)
            Log.i("MainActivity", "插入数据 S1 : " + s1)

            Thread.sleep(500)

            viewModel.insert(s2)
            Log.i("MainActivity", "插入数据 S2 : " + s2)

            Thread.sleep(500)

            s2 = Student(2, "Jack", 60)
            viewModel.update(s2)
            Log.i("MainActivity", "更新数据 S2 : " + s2)

            Thread.sleep(500)

            // 删除数据
            viewModel.delete(1)
            Log.i("MainActivity", "删除数据 id = 1")

            Thread.sleep(500)

            var students = viewModel.repository.dao.query()
            Log.i("MainActivity", "主动查询 : LiveData : " + students + " , 实际数据 : " + students?.value)

            var students2 = viewModel.repository.dao.queryList()
            Log.i("MainActivity", "主动查询2 : " + students2)
        }

7、Room 框架主动查询数据库数据需保留除 LiveData 返回值外的正常查询方法


Room 框架 与 LiveData 结合使用之后 , 在 Room 框架中的 Dao 数据库访问接口中 定义了 LiveData 返回值类型的查询方法 ;

    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun query(): LiveData<List<Student>>

    /**
     * 根据传入的 id 查询数据库表
     * 在注解中使用 :id 调用参数中的 id: Int
     */
    @Query("select * from student where id = :id")
    fun query(id: Int): LiveData<List<Student>>

上述定义的 fun query(): LiveData<List<Student>> 查询方法 , 只能在数据库数据发生改变被动回调时才能查询出数据 , 如果主动调用该方法查询数据库 , 会返回一个空数据的 LiveData ;


如果想要手动主动查询数据库 , 需要保留非 LiveData 返回值的查询方法 , 也就是如下面的代码所示 , 同时维护两组查询方法接口 ,

  • 与 LiveData 交互的接口 , 返回 LiveData<List<Student>> 类型 返回值 ;
  • 手动主动调用的查询 数据库的 方法接口 , 返回 List<Student> 类型 返回值 ;
    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun query(): LiveData<List<Student>>

    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun queryList(): List<Student>




二、完整代码示例




1、build.gradle 构建脚本


在 build.gradle 构建脚本 中 , 需要配置 Kotlin 插件 和 Kotlin 注解插件 ;

plugins {
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
}

导入 Room 依赖库 , 注意这是 Kotlin 版本需要导入的依赖库 , 如果是 Java 版本 , 需要导入另外的注解处理器 ;

    // 导入 Room 依赖库
    implementation 'androidx.room:room-runtime:2.2.5'
    // 导入注解处理器 ( Kotlin )
    kapt 'androidx.room:room-compiler:2.2.5'

完整代码 :

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
}

android {
    namespace 'kim.hsl.rvl'
    compileSdk 32

    defaultConfig {
        applicationId "kim.hsl.rvl"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

    viewBinding {
        // 启用 ViewBinding
        enabled = true
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // 导入 Room 依赖库
    implementation 'androidx.room:room-runtime:2.2.5'
    // 导入注解处理器 ( Kotlin )
    kapt 'androidx.room:room-compiler:2.2.5'
    // 导入注解处理器 ( Java )
    //annotationProcessor 'androidx.room:room-compiler:2.2.5'
}

2、Room 框架相关代码


Entity 实体类

Entity 实体类 使用 @Entity 注解修饰 , 并使用 @PrimaryKey 注解修饰主键 , 使用 @ColumnInfo 注解 修饰普通字段 , 使用 @Ignore 注解 修饰不需要的字段或方法 ;

完整代码 :

package kim.hsl.rvl

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey

/**
 * 定义数据库表 Entity 实体 / 同时定义数据库表 和 对鹰的实体类
 * 设置该数据类对应数据库中的一张数据表, 表名为 student
 * 该数据库表中的数据对应一个 Student 类实例对象
 */
@Entity(tableName = "student")
class Student {
    /**
     * @PrimaryKey 设置主键 autoGenerate 为自增
     * @ColumnInfo name 设置列名称 / typeAffinity 设置列类型
     */
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
    var id: Int = 0

    /**
     * 姓名字段
     * 数据库表中的列名为 name
     * 数据库表中的类型为 TEXT 文本类型
     */
    @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
    lateinit var name: String

    /**
     * 年龄字段
     * 数据库表中的列名为 age
     * 数据库表中的类型为 INTEGER 文本类型
     */
    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    var age: Int = 0

    /**
     * 有些属性用于做业务逻辑
     * 不需要插入到数据库中
     * 使用 @Ignore 注解修饰该属性字段
     */
    @Ignore
    lateinit var studentInfo: String

    /**
     * 默认的构造方法给 Room 框架使用
     */
    constructor(id: Int, name: String, age: Int) {
        this.id = id
        this.name = name
        this.age = age
    }

    /**
     * 使用 @Ignore 标签标注后
     * Room 就不会使用该构造方法了
     * 这个构造方法是给开发者使用的
     */
    @Ignore
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }

    /**
     * 使用 @Ignore 标签标注后
     * Room 就不会使用该构造方法了
     * 这个构造方法是给开发者使用的
     */
    @Ignore
    constructor(id: Int) {
        this.id = id
    }

    override fun toString(): String {
        return "Student(id=$id, name='$name', age=$age)"
    }
}

Dao 数据库访问接口对象

Room 框架中的 Dao 数据库访问对象接口 的定义方式需要作出改变 , 涉及到数据库查询的 接口方法时 , 其返回值需要 返回 LiveData 类型 , 泛型设置为 List<Student> 类型 ;

完整代码 :

package kim.hsl.rvl

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update

/**
 * 数据库访问对象接口 / 使用 @Dao 注解修饰
 * 提供数据库的增删改查方法
 */
@Dao
interface StudentDao {
    /**
     * 向数据库表中插入元素
     */
    @Insert
    fun insert(student: Student)

    /**
     * 从数据库表中删除元素
     */
    @Delete
    fun delete(student: Student)

    /**
     * 修改数据库表元素
     */
    @Update
    fun update(student: Student)

    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun query(): LiveData<List<Student>>

    /**
     * 根据传入的 id 查询数据库表
     * 在注解中使用 :id 调用参数中的 id: Int
     */
    @Query("select * from student where id = :id")
    fun query(id: Int): LiveData<List<Student>>

    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun queryList(): List<Student>

    /**
     * 根据传入的 id 查询数据库表
     * 在注解中使用 :id 调用参数中的 id: Int
     */
    @Query("select * from student where id = :id")
    fun queryList(id: Int): List<Student>
}

Database 数据库实体类

Database 数据库实体类 使用 @Database 注解修饰该类 , 其中定义 获取 Dao 数据库访问对象的抽象方法 , 以及 将该抽象类设置成 单例类 , 在单例对象初始化时创建数据库 ;

完整代码 :

package kim.hsl.rvl

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [Student::class], version = 1, exportSchema = false)
abstract class StudentDatabase: RoomDatabase() {
    /**
     * 获取 数据库访问 对象
     * 这是必须要实现的函数
     */
    abstract fun studentDao(): StudentDao

    companion object {
        lateinit var instance: StudentDatabase

        fun inst(context: Context): StudentDatabase {
            if (!::instance.isInitialized) {
                synchronized(StudentDatabase::class) {
                    // 创建数据库
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        StudentDatabase::class.java,
                        "student_database.db")
                        .allowMainThreadQueries() // Room 原则上不允许在主线程操作数据库
                                                  // 如果要在主线程操作数据库需要调用该函数
                        .build()
                }
            }
            return instance;
        }
    }
}

3、Repository 代码


Repository 代码 负责 封装 Model 数据模型层 , 用于与 ViewModel 层进行交互 ;

package kim.hsl.rvl

import android.content.Context
import androidx.lifecycle.LiveData

class Repository {
    lateinit var dao: StudentDao

    constructor(context: Context) {
        var database = StudentDatabase.inst(context)
        this.dao = database.studentDao()
    }

    fun insert(student: Student) {
        this.dao.insert(student)
    }

    fun query(): LiveData<List<Student>> {
        return this.dao.query()
    }

    fun update(student: Student) {
        this.dao.update(student)
    }

    fun delete(id: Int) {
        var student = Student(id)
        this.dao.delete(student)
    }
}

4、ViewModel 代码


负责数据的维护 , 显示到 View 组件中 ;


完整代码 :

package kim.hsl.rvl

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData

class ViewModel: AndroidViewModel {

    lateinit var repository: Repository

    constructor(application: Application) : super(application) {
        this.repository = Repository(application)
    }

    fun insert(student: Student) {
        this.repository.insert(student)
    }

    fun query(): LiveData<List<Student>> {
        return this.repository.query()
    }

    fun update(student: Student) {
        this.repository.update(student)
    }

    fun delete(id: Int) {
        this.repository.delete(id)
    }
}

5、Activity 组件中的最终调用代码


通过调用 ViewModel 视图模型 , 访问 Room 数据库框架 , 对数据进行增删改查 , 并通过 LiveData 监听数据库中的数据 ,

如果数据库中的数据发生改变 , 自动回调 LiveData 的 Observer 监听器中的 onChanged 回调方法 ;

完整代码 :

package kim.hsl.rvl

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
import kim.hsl.rvl.databinding.ActivityMainBinding
import kotlin.concurrent.thread

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(getLayoutInflater())
        setContentView(binding.root)

        // 获取 ViewModel 视图模型对象
        var viewModel: ViewModel = ViewModelProvider(
            this,
            AndroidViewModelFactory(application)).get(ViewModel::class.java)

        // 为 ViewModel 中获取的 LiveData 数据设置 Observer 监听
        viewModel.query().observe(this, object: Observer<List<Student>> {
            override fun onChanged(t: List<Student>?) {
                Log.i("MainActivity", "Observer#onChanged 回调, List<Student>: " + t)
            }
        })

        thread(start = true) {

            Thread.sleep(500)

            // 插入数据
            var s1 = Student("Tom", 18)
            var s2 = Student("Jerry", 16)

            viewModel.insert(s1)
            Log.i("MainActivity", "插入数据 S1 : " + s1)

            Thread.sleep(500)

            viewModel.insert(s2)
            Log.i("MainActivity", "插入数据 S2 : " + s2)

            Thread.sleep(500)

            s2 = Student(2, "Jack", 60)
            viewModel.update(s2)
            Log.i("MainActivity", "更新数据 S2 : " + s2)

            Thread.sleep(500)

            // 删除数据
            viewModel.delete(1)
            Log.i("MainActivity", "删除数据 id = 1")

            Thread.sleep(500)

            var students = viewModel.repository.dao.query()
            Log.i("MainActivity", "主动查询 : LiveData : " + students + " , 实际数据 : " + students?.value)

            var students2 = viewModel.repository.dao.queryList()
            Log.i("MainActivity", "主动查询2 : " + students2)
        }
    }
}

6、执行结果


由下面的执行结果可以得出如下结论 :

  • 为 ViewModel 中的数据库查询方法 获取的 LiveData , 首次设置 Observer 监听 , 会回调一次, 首次查询时 , 数据库为空 , 没有查到任何数据 , 最终得到 [] 打印结果 ;
        // 为 ViewModel 中获取的 LiveData 数据设置 Observer 监听
        viewModel.query().observe(this, object: Observer<List<Student>> {
            override fun onChanged(t: List<Student>?) {
                Log.i("MainActivity", "Observer#onChanged 回调, List<Student>: " + t)
            }
        })
  • 第一次 插入数据 S1 , 数据库数据发生改变 , 自动触发 Observer#onChanged 回调 , 此时数据库中有数据 [Student(id=1, name='Tom', age=18)] ;
  • 第二次 插入数据 S2 , 数据库数据发生改变 , 自动触发 Observer#onChanged 回调 , 此时数据库中有数据 [Student(id=1, name='Tom', age=18), Student(id=2, name='Jerry', age=16)] ;
  • 更新数据 S2 时 , 数据库数据发生改变 , 自动触发 Observer#onChanged 回调 , 此时数据库中有数据 [Student(id=1, name='Tom', age=18), Student(id=2, name='Jack', age=60)] , id 为 2 的数据内容发生了改变 ;
  • 删除 id = 1 的数据 , 数据库数据发生改变 , 自动触发 Observer#onChanged 回调 , 此时数据库中有数据 [Student(id=2, name='Jack', age=60)] ;
  • 调用 Dao 中返回 LiveData 的接口方法查询数据库 , 返回 androidx.room.RoomTrackingLiveData@8726677 , 但其中的数据为空 ;
  • 调用 Dao 中返回 List<Student> 的接口方法查询数据库 , 返回数据为 [Student(id=2, name='Jack', age=60)] ;

执行结果 :

2023-05-24 16:49:49.225 I/MainActivity: Observer#onChanged 回调, List<Student>: []
2023-05-24 16:49:49.475 I/MainActivity: 插入数据 S1 : Student(id=0, name='Tom', age=18)
2023-05-24 16:49:49.481 I/MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18)]
2023-05-24 16:49:49.979 I/MainActivity: 插入数据 S2 : Student(id=0, name='Jerry', age=16)
2023-05-24 16:49:49.981 I/MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name='Jerry', age=16)]
2023-05-24 16:49:50.482 I/MainActivity: 更新数据 S2 : Student(id=2, name='Jack', age=60)
2023-05-24 16:49:50.484 I/MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name='Jack', age=60)]
2023-05-24 16:49:50.996 I/MainActivity: 删除数据 id = 1
2023-05-24 16:49:51.009 I/MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=2, name='Jack', age=60)]
2023-05-24 16:49:51.497 I/MainActivity: 主动查询 : LiveData : androidx.room.RoomTrackingLiveData@8726677 , 实际数据 : null
2023-05-24 16:49:51.500 I/MainActivity: 主动查询2 : [Student(id=2, name='Jack', age=60)]

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/566148.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RWKV论文燃爆!将RNN崛起进行到底!可扩百亿级参数,与Transformer表现相当!

深度学习自然语言处理 原创作者&#xff1a;鸽鸽 万众期待的RWKV论文来啦&#xff01; 这股RNN崛起的“清流”&#xff0c;由民间开源组织发起&#xff0c;号称是第一个可扩展到百亿级参数的非transformer架构&#xff01; RWKV结合了RNN和Transformer的优势&#xff1a;一方面…

【送书】前端系列16集-vue3范型,vue-i18n-next,watch,watchEffect

送书活动&#xff1a;挑选1名粉丝同学哦 免费包邮送。截止时间&#xff1a;2023/5/26号 19 : 00参与底部评论区说说&#xff1a;请在评论中分享你的阅读收获。 中台落地手记——业务服务化与数据资产化 vue-i18n-next vue3 中使用 i18n 需要安装的是 [vue-i18n v9] 的版本 npm…

Nat Biotechnol –精准 CRISPR-Cas噬菌体疗法将为重症感染患者带来福音

治疗血液系统恶性肿瘤的化学药物常会引起骨髓功能抑制&#xff08;bone marrow suppression&#xff09;和胃肠道黏膜炎&#xff0c;并伴有肠道通透性增加。肠道细菌&#xff08;包括大肠杆菌&#xff09;从胃肠道易位是血流感染的常见原因。肠道细菌引起血流感染导致的死亡率为…

【这个问题纠结了我好多年】3dMax到底使用Intel还是AMD的CPU更好?

随着英特尔和 AMD 的新 CPU上市&#xff0c;是时候进行新一轮的硬件测试了。通过以极具竞争力的价格提供大量内核&#xff0c;AMD 已成为 CPU 领域的有力竞争者。作为回应&#xff0c;英特尔已开始增加其 CPU 中的内核数量。虽然它们提供的内核数量仍然不如 AMD&#xff0c;但它…

基于ESP32-CAM 和 OpenCV 设计的手势控制虚拟鼠标

概述 在本文中,我们将使用ESP32-CAM和OpenCV开发手势控制虚拟鼠标。ESP32 Camera Module和Python程序可用于无线控制鼠标跟踪和点击操作。 入门者必须具备 Python、图像处理、嵌入式系统以及物联网的丰富知识。首先,我们将了解如何控制鼠标跟踪和单击,以及运行 python 程序…

《Kali渗透基础》03. 被动信息收集

kali渗透 1&#xff1a;被动信息收集1.1&#xff1a;收集内容1.2&#xff1a;信息用途 2&#xff1a;域名信息收集2.1&#xff1a;nslookup2.1.1&#xff1a;命令参数2.1.2&#xff1a;示例 - 命令行2.1.3&#xff1a;示例 - 交互式 2.2&#xff1a;dig2.2.1&#xff1a;命令参…

chatgpt赋能python:PythonSplit连续空格

Python Split 连续空格 在Python编程中&#xff0c;split()方法是用来将字符串按照指定的分隔符划分成一个列表。默认情况下&#xff0c;分隔符是空格。但是&#xff0c;在实际应用中&#xff0c;我们可能会遇到连续空格的情况&#xff0c;这时候split()方法会出现一些问题。本…

让你不再好奇怎样无损放大图片

随着科技的不断进步&#xff0c;放大图片已经成为我们生活中不可避免的需求。但是&#xff0c;放大图片往往会导致图片失真、模糊或者变形等问题&#xff0c;让人感到十分困扰。那么&#xff0c;你知道怎样无损放大图片吗&#xff1f;接下来我将分享三个无损放大图片的方法给你…

顺丰科技携手飞桨自研“智能外呼机器人”,为客户打造优质服务体验

“您好&#xff0c;请问是李立先生吗”&#xff0c;或许不少人在拨通客服电话后发现是机器人客服&#xff0c;都希望能快点转人工。但顺丰的“客服机器人”却是“与众不同”的存在。 顺丰已成为国内领先的快递物流综合服务商、全球第四大快递公司&#xff0c;依托领先的科技研发…

DailyMart02:DDD领域分解与微服务划分

大家好&#xff0c;今天咱们继续更新DDD&微服务系列&#xff01; DailyMart是一个简单的购物商城&#xff0c;主要销售书籍&#xff0c;包括实体书和电子书。本文将使用领域驱动设计&#xff08;DDD&#xff09;对DailyMart的业务进行分析与优化&#xff0c;以提高系统的内…

今天的技术干货由 ChatGPT 买单了~~

ChatGPT 技术最近有多火就不用再介绍了吧&#xff0c;连超级大佬都说了 ChatGPT 这是几百年不遇的、类似发明电的工业革命一样的机遇。 这种机遇当然不能错过&#xff0c;使用得当那就像玄幻小说里的男主角开了挂一样&#xff0c;用来做快速查询、资料搜集、辅助学习相当不错&a…

ip网络广播对讲的特点

随着科技的不断发展&#xff0c;通讯方式也在不断地变革。传统的对讲机已经无法满足现代化沟通的需求&#xff0c;特别是在大型企业、学校和安保等领域对讲机的局限性已经显现出来。而一种新型通讯方式&#xff1a;IP网络广播对讲正在逐渐受到人们的关注和使用。本篇文章将围绕…

chatgpt赋能python:Python工程师必知:掌握Pythonspdiags用于高效稀疏矩阵计算

Python工程师必知&#xff1a;掌握Python spdiags用于高效稀疏矩阵计算 在机器学习和数据分析中&#xff0c;我们常常需要处理大量的数据集来进行模型训练和预测&#xff0c;但是在实际应用中&#xff0c;很多数据集都是稀疏的。这时候&#xff0c;稀疏矩阵的计算就变得非常重…

2023年网络安全自治区职业院校技能大赛暨全国职业院校技能大赛新疆选拔赛任务书

2023年自治区职业院校技能大赛暨全国职业院校技能大赛新疆选拔赛任务书 一、竞赛时间 总计&#xff1a;360分钟 竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略配置 A-3 流量完整性保护 A-4 事件…

linux安装并启动nacos

前提准备 下载最新稳定版本&#xff0c;此处以nacos-server-2.1.0.tar.gz版本为例安装下载地址&#xff1a;https://github.com/alibaba/nacos/releases 二、具体步骤2.1 下载完成后解压&#xff1a;tar -zxvf nacos-server-2.1.0.tar.gz 2.2 将解压文件移动到/usr/local目录下…

生活中有趣好玩的产品设计

生活纷繁忙碌&#xff0c;设计无处不在。我们的衣食住行、吃喝玩乐都在跟设计打交道&#xff0c;创作奇才们用竭尽所能的心智引导和体验设计&#xff0c;吸引着我们的注意力。 这其中充满着做产品的思路&#xff0c;散发着智慧的光芒&#xff0c;留心观察就会发现很多有趣好玩的…

机器视觉陶瓷板外观检测设备有哪些优点?

随着制造业的不断发展&#xff0c;各种各样的产品被生产出来&#xff0c;其中陶瓷板是一种被广泛应用的材料。然而&#xff0c;由于制造过程中的各种因素&#xff0c;陶瓷板的表面可能存在各种缺陷&#xff0c;比如裂纹、气泡、凹凸不平等问题&#xff0c;这些问题会影响到产品…

惊呆!用streamlit快速搭建炫酷站点!

大家注意&#xff1a;因为微信最近又改了推送机制&#xff0c;经常有小伙伴说错过了之前被删的文章&#xff0c;比如前阵子冒着风险写的爬虫&#xff0c;再比如一些限时福利&#xff0c;错过了就是错过了。 所以建议大家加个星标&#xff0c;就能第一时间收到推送。&#x1f44…

C++ MFC 学习笔记+小型通讯录系统实现

MFC 最详细入门教程 [MFC常用函数总结]&#xff08;https://www.cnblogs.com/jiu0821/p/4606639.html&#xff09; [C & MFC]https://www.cnblogs.com/gaohongchen01/p/4176963.html [MFC入门&#xff08;一&#xff09;]https://www.cnblogs.com/yangyuqing/p/10283641…

FPGA远程更新/远程调试的一种简单方法

之前介绍过一种远程&#xff08;无线&#xff09;更新的方式&#xff0c;详见《起飞&#xff01;通过无线WIFI下载调试FPGA》&#xff0c;这种方式缺点有两个&#xff1a;一是速度较慢&#xff1b;二是我们的设备中需要增加一个无线设备&#xff0c;增加成本的同时增加了暴露的…