在build.gradle (Module: app)中
apply plugin: 'kotlin-kapt'
...
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-core:$rootProject.coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"
}
kotlin {
experimental {
coroutines "enable"
}
}
在project的build.gradle 中添加版本号
ext {
roomVersion = '2.1.0-alpha06'
archLifecycleVersion = '2.1.0-alpha04'
androidxArchVersion = '2.0.0'
coroutines = '1.2.0'
}
data class Word(val word: String)
为其添加注解,使其对Room数据库有意义
@Entity(tableName = "word_table")
class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)
什么是DAO?
suspend
修饰符进行注解,并从协序或其他suspend 函数调用。实现DAO
让我们编写一个DAO,它提供查询以获取所有单词,插入单词和删除所有单词。
1.创建一个新接口WordDao并调用它。
2.对类使用注解@Dao将其标识为Room的DAO类。
3.声明一个suspend 方法来插入一个单词:suspend fun insert(word: Word)
4.使用注解@Insert。您不必提供任何SQL!(也有@Delete和@Update注解用于删除和更新一行,但是这个应用没有用到他们。)
5.声明删除所有单词的方法:fun deleteAll()。
6.删除多个实体没有方便的注解,因此使用泛型注释该方法@Query。
7.将SQL查询作为字符串参数提供给@Query注释。
@Query(“DELETE FROM word_table”)
8.创建一个方法来获得所有的返回值。
fun getAllWords(): List
9.使用SQL查询注释方法:
@Query(“SELECT * from word_table ORDER BY word ASC”)
这是完成的代码:
@Dao
interface WordDao {
@Query("SELECT * from word_table ORDER BY word ASC")
fun getAllWords(): List<Word>
@Insert
suspend fun insert(word: Word)
@Query("DELETE FROM word_table")
fun deleteAll()
}
当数据发生变化时,您通常需要采取一些措施,例如在UI中显示更新的数据。这意味着您必须观察数据,以便在更改时,您可以做出反应。
根据数据的存储方式,这可能很棘手。观察应用程序的多个组件之间的数据更改可以在组件之间创建明确,严格的依赖关系路径。这使得测试和调试变得困难等等。
LiveData
,一个用于数据观察的生命周期库类,解决了这个问题。在方法描述中使用LiveData类型的返回值,当数据库更新时,room会生成所有必要的代码来更新LiveData。
如果独立于Room使用LiveData,您必须管理更新数据,LiveData没有公开的方法来更新存储的数据。
如果要更新存储的数据,必须使用MutableLiveData
而不是LiveData
,MutableLiveData类有两个公共方法setValue(T)
,postValue(T)
,允许您设置LiveData对象的值。通常,ViewModel中使用MutableLiveData,然后ViewModel只向观察者公开不可变的LiveData对象。
@Query("SELECT * from word_table ORDER BY word ASC")
fun getAllWords(): LiveData<List<Word>>
什么是Room
LiveData
,查询将在后台线程上自动运行。您的Room数据库类必须是抽象的并扩展RoomDatabase。通常,整个应用程序只需要一个Room数据库实例。
1.创建一个public abstract 类WordRoomDatabase ,继承RoomDatabase
2.将类注释为Room数据库,声明属于数据库的实体,并设置版本号。列出实体将在数据库中创建表。
3.定义与数据库一起使用的DAO。为每个@Dao提供一个抽象的“getter”方法。
@Database(entities = [Word::class], version = 1)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
}
4.将WordRoomDatabase设置为单例,以防止同时打开数据库的多个实例。
@Database(entities = arrayOf(Word::class), version = 1)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): WordRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"Word_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
修改数据库架构时,您需要更新版本号并定义迁移策略
作为一个样本,销毁和重新创建策略就足够了。但是,对于真正的应用程序,您必须实施迁移策略。请参阅了解使用Room进行迁移。
Repository类抽象了对多个数据源的访问。Repository不是体系结构组件库的一部分,而是代码分离和体系结构的建议最佳实践。Repository类为应用程序其余部分的数据访问提供了API。
Repository管理查询并允许您使用多个后端。在最常见的示例中,Repository实现了决定是从网络中获取数据还是使用本地数据库中缓存的结果的逻辑。
实现Repository
1.创建一个名为的public class WordRepository。
2.将DAO声明为构造函数中的私有属性
3.将单词列表添加为公共属性并对其进行初始化。Room在单独的线程上执行所有查询。当数据发生变化LiveData就会通知观察者。
4.将单词列表添加为公共属性并初始化它。room在单独的线程上执行所有查询。LiveData将在数据更改时通知观察者。
5.为insert()方法添加包装。您必须在非UI线程上调用它,否则您的应用程序将崩溃。Room确保您不会在主线程上执行任何长时间运行的操作,从而阻塞UI。添加@workerthread注释,以标记需要从非UI线程调用此方法。添加suspend修饰符以告诉编译器需要从协程或其他suspend 函数调用此修饰符。
class WordRepository(private val wordDao: WordDao) {
val allWords: LiveData<List<Word>> = wordDao.getAllWords()
@WorkerThread
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}
ViewModel向UI提供数据,并在配置更改后继续运行。充当存储库和UI之间的通信中心。还可以使用ViewModel在fragment之间共享数据。视图模型是lifecycle library的一部分。
ViewModel以一种生命周期意识的方式保存应用程序的UI数据,这种方式可以在配置更改后存活下来。将应用程序的UI数据与活动类和片段类分离可以让您更好地遵循单一的责任原则:activity和fragment负责将数据绘制到屏幕上,而ViewModel可以负责保存和处理UI所需的所有数据。
//创建一个名为WordViewModel的类,该类将应用程序作为参数并扩展AndroidViewModel。
class WordViewModel(application: Application) : AndroidViewModel(application) {
//添加私有成员变量以保存对存储库的引用。
private val repository: WordRepository
//使用LiveData并缓存getAlphabetizedWords返回的内容有几个好处:
//我们可以将观察者放在数据上(而不是轮询更改)并只更新
//数据实际更改时的UI。
//存储库通过ViewModel与UI完全分离。
//添加私有的livedata成员变量以缓存单词列表。
val allWords: LiveData<List<Word>>
//创建一个init块,该块从WordRoomDatabase获取对WordDAO的引用,并基于它构造WordRepository。
init {
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
repository = WordRepository(wordsDao)
//在init块中,使用来自存储库的数据初始化allWords属性:
allWords = repository.allWords
}
/**
* 启动新协程以非阻塞方式插入数据
* 创建一个包装insert()方法,该方法调用存储库的insert()方法。
* 这样,insert()的实现就完全隐藏在UI中。
* 我们希望从主线程调用insert()方法,
* 因此我们将基于前面定义的协程范围启动一个新的协程。
* 因为我们正在执行数据库操作,所以我们使用的是IO调度程序。
*/
fun insert(word: Word) = viewModelScope.launch {
repository.insert(word)
}
}
重要提示:ViewModel不是OnSaveInstanceState()方法的替代品,因为ViewModel在进程关闭后无法生存。