LiveData、Room、ViewModel

Posted by アライさん on 2019年10月22日

https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/index.html

Gradle配置

app的build.gradle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apply plugin: 'kotlin-kapt'
android {
packagingOptions {
exclude 'META-INF/atomicfu.kotlin_module'
}
}
dependencies {
// Dependencies for working with Architecture components
// You'll probably have to update the version numbers in guild.gradle (Project)
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
implementation "androidx.room:room-ktx:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"

implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"
kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
}

project的build.gradle:

1
2
3
4
5
6
ext {
roomVersion = '2.1.0-alpha07'
archLifecycleVersion = '2.2.0-alpha01'
androidxArchVersion = '2.0.0'
coroutines = '1.2.0'
}

数据实体:

1
2
3
4
5
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "word_table")
class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)

Dao:

1
2
3
4
5
6
7
8
9
@Dao
interface WordDao {
@Query("SELECT * from word_table ORDER BY word ASC")
fun getAllWords(): LiveData<List<Word>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(word: Word)
@Query("DELETE FROM word_table")
suspend fun deleteAll()
}

RoomDatabase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch


@Database(entities = [Word::class], version = 1)
abstract class WordRoomDatabase : RoomDatabase() {

abstract fun wordDao(): WordDao

companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null

fun getDatabase(
context: Context,
scope: CoroutineScope
): WordRoomDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
)
//如果数据库升级,无法迁移时,删除重建
.fallbackToDestructiveMigration()
.addCallback(WordDatabaseCallback(scope))
.build()
INSTANCE = instance
// return instance
instance
}
}

private class WordDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
/**
* Override the onOpen method to populate the database.
* For this sample, we clear the database every time it is created or opened.
*/
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
// If you want to keep the data through app restarts,
// comment out the following line.
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.wordDao())
}
}
}
}

/**
* 获取新数据,覆盖数据库中的记录
* 如果只是想要新增,add就好。
*/
suspend fun populateDatabase(wordDao: WordDao) {
wordDao.deleteAll()

var word = Word("Hello")
wordDao.insert(word)
word = Word("World!")
wordDao.insert(word)
}
}

}

Repository:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Declares the DAO as a private property in the constructor. Pass in the DAO
// instead of the whole database, because you only need access to the DAO
class WordRepository(private val wordDao: WordDao) {

// Room executes all queries on a separate thread.
// Observed LiveData will notify the observer when the data has changed.
val allWords: LiveData<List<Word>> = wordDao.getAllWords()


// The suspend modifier tells the compiler that this must be called from a
// coroutine or another suspend function.
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}

ViewModel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Class extends AndroidViewModel and requires application as a parameter.
class WordViewModel(application: Application) : AndroidViewModel(application) {

// The ViewModel maintains a reference to the repository to get data.
private val repository: WordRepository
// LiveData gives us updated words when they change.
val allWords: LiveData<List<Word>>

init {
// Gets reference to WordDao from WordRoomDatabase to construct
// the correct WordRepository.
val wordsDao = getDatabase(application, viewModelScope).wordDao()
repository = WordRepository(wordsDao)
allWords = repository.allWords
}

// The implementation of insert() is completely hidden from the UI.
// We don't want insert to block the main thread, so we're launching a new
// coroutine. ViewModels have a coroutine scope based on their lifecycle called
// viewModelScope which we can use here.
fun insert(word: Word) = viewModelScope.launch {
repository.insert(word)
}
}

调用:

1
2
3
4
5
6
7
8
private lateinit var wordViewModel: WordViewModel
wordViewModel = ViewModelProviders.of(this).get(WordViewModel::class.java)
wordViewModel.allWords.observe(this, Observer { words ->
// Update the cached copy of the words in the adapter.
words?.let { adapter.setWords(it) }
})
//插入
wordViewModel.insert(word)

如果fragment和activity共用ViewModel,可以用

1
2
3
activity?.run {
ViewModelProviders.of(this)[AndroidViewModel::class.java]
}