重新学习Android——(六)Room官方示例
本篇笔记记录的是Android的官方Codelab——带 View 的 Android Room - Kotlin中的内容。
Room
Room是一个安卓平台的ORM框架,它在Sqlite上提供了一个抽象层,让你不必再繁琐的使用SqliteOpenHelper进行操作。
概念
Room中提供如下概念
- 数据库:Sqlite中的一个数据库,在Room中使用
@Database
注解标注。一般一个应用程序只建立一个数据库,并且这个数据库对象在程序中全局单例。 - 实体:实体即数据库中的一个表,在Room中使用
@Entity
注解标注,在Kotlin代码中表现为一个数据类,Room会自动完成数据库表到数据类之间的转换。 - DAO:数据访问对象,用于操作数据库完成增删改查操作,在Room中使用
@Dao
注解标注
依赖
dependencies {
def roomVersion = "2.4.0"
def lifecycleVersion = "2.4.0"
def coroutineVersion = "1.6.0"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
// Activity
implementation 'androidx.activity:activity-ktx:1.4.0'
// Room
implementation "androidx.room:room-ktx:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion"
androidTestImplementation "androidx.room:room-testing:$roomVersion"
// Lifecycle
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
// Kotlin components
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
同时启用kapt
plugins {
// other plugins ...
id 'kotlin-kapt'
}
创建实体
@Entity(tableName = "word_table")
data class Word(
@PrimaryKey
@ColumnInfo(name = "word")
val word: String
)
创建DAO
@Dao
interface WordDao {
@Query("SELECT * FROM word_table")
fun getAlphabetizedWords(): Flow<List<Word>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(word: Word)
@Query("DELETE FROM word_table")
suspend fun deleteAll()
}
Dao是抽象类或接口,同时它的方法可以被声明成suspend,之后可以在协程中调用
创建数据库
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
// 该类只是为了测试使用,在每次数据库实例创建后删除word表中所有数据,添加两条测试数据
private class WordDataBaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { db ->
scope.launch {
var wordDao = db.wordDao()
wordDao.deleteAll()
wordDao.insert(Word("Hello"))
wordDao.insert(Word("Word"))
}
}
}
}
// 这个dao实现类应该是会在编译时自动创建??官方教程里也没写,就一下带过了。
// 反正,和大部分ORM框架一样,这里就是一些魔法,Dao实现类会自动创建
abstract fun wordDao(): WordDao
companion object {
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): WordRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
).addCallback(WordDataBaseCallback(scope)).build()
INSTANCE = instance
instance
}
}
}
}
Repository
根据应用架构指南,我们抽象出一个Repository层,这个Repository依赖wordDao,这个wordDao会在外部被注入
class WordRepository(private val wordDao: WordDao) {
val allWords: Flow<List<Word>> = wordDao.getAlphabetizedWords()
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}
Application
创建Application协程作用域,创建database实例和repository实例。
我们希望数据库实例和respository实例跟随应用的作用域,所以我们在这里创建它们
class WordApplication : Application() {
val applicationScope = CoroutineScope(SupervisorJob())
val database by lazy { WordRoomDatabase.getDatabase(this, applicationScope) }
val repository by lazy { WordRepository(database.wordDao()) }
}
MainActivity
这里只有和viewModel交互相关的代码,完整代码可以去看官方Codelab
private val wordViewModel: WordViewModel by viewModels {
WordViewModelFactory((application as WordApplication).repository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
wordViewModel.allWords.observe(this) {
adapter.submitList(it)
}
}
下面是利用从另一个Activity返回的信息来插入单词
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK && requestCode == newWordActivityRequestCode) {
data?.getStringExtra(NewWordActivity?.EXTRA_REPLY)?.let {
wordViewModel.insert(Word(it))
}
} else {
Toast.makeText(this, R.string.empty_not_saved, Toast.LENGTH_SHORT).show()
}
}