Compose 和 Room:使用存储库初始化 ViewModel 时出现问题
Compose and Room: Issue with initializing ViewModel with a repository
我目前正在关注 Android Room with a View codelab 并尝试将其与 Jetpack Compose 一起采用。我卡在了在 compose 函数中初始化 viewModel。
我得到的错误:
None of the following functions can be called with the arguments supplied:
public inline fun <reified VM : ViewModel> viewModel(viewModelStoreOwner: ViewModelStoreOwner = ..., key: String? = ..., factory: ViewModelProvider.Factory? = ...): TypeVariable(VM) defined in androidx.lifecycle.viewmodel.compose
public fun <VM : ViewModel> viewModel(modelClass: Class<TypeVariable(VM)>, viewModelStoreOwner: ViewModelStoreOwner = ..., key: String? = ..., factory: ViewModelProvider.Factory? = ...): TypeVariable(VM) defined in androidx.lifecycle.viewmodel.compose
@Composable
fun WordBookApp() {
val context = LocalContext.current
val wordViewModel: WordViewModel by viewModel( // error here - viewModel
WordViewModelFactory((context.applicationContext as WordsApplication).repository)
)
val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())
...
视图模型和视图模型工厂:
class WordViewModel(private val repository: WordRepository) : ViewModel() {
val allWords: LiveData<List<Word>> = repository.allWords.asLiveData()
fun insert(word: Word) = viewModelScope.launch {
repository.insert(word)
}
}
class WordViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(WordViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return WordViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
其他部分代码:
class WordRepository(private val wordDao: WordDao) {
val allWords: Flow<List<Word>> = wordDao.getAlphabetizedWords()
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}
class WordsApplication : Application() {
private val database by lazy { WordRoomDatabase.getDatabase(this) }
val repository by lazy { WordRepository(database.wordDao()) }
}
@Database(entities = [Word::class], version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): 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"
).build()
INSTANCE = instance
// return instance
instance
}
}
}
}
AndroidMenifest.xml
<application
android:name=".WordsApplication"
....
有人可以帮忙吗?谢谢!
既然你没有提到这个问题,我已经开始打字了。这可能与您的问题无关,但也许您仍然应该阅读此内容。
如果问题是视图模型中的值没有更新,- 在可组合的内部初始化视图模型,这是非常糟糕的主意。
您看到可组合项经常重新组合,重新执行其中的每一行代码。因此,如果您像这样初始化此可组合项内的视图模型,它将在每次重新组合时重新初始化。理论上,即使在帧速率下也可以进行重组(在许多情况下甚至可以)。因此,这不是在可组合项中声明变量的方法。
好的,remember
可组合项可以帮助您解决这个问题。如果用 remember
包装初始化语句,它不会在重组时重新初始化。但是,它有其局限性。例如,如果可组合项被销毁,例如,如果您将其滑出屏幕,remember
ed 值就会丢失。 remember
随着包含它的可组合项的销毁而被销毁。
因此,对于动画之类的小东西,可以将变量存储在可组合项中,但对于重要的东西,你不应该相信这个框架。
因此,最好的方法是在主 activity 中初始化视图模型,然后将其方法和变量传递给可组合项。您甚至可以传递视图模型本身,但大多数时候不需要。
代码:
@Composable
fun WordBookApp() {
val context = LocalContext.current
val wordViewModel: WordViewModel by remember {
viewModel( // error here - viewModel
WordViewModelFactory((context.applicationContext as WordsApplication).repository)
)
}
val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())
...
从@MARSK 的回答中得到了线索并修复了它。将视图模型的初始化移动到 MainActivity
的 onCreate()
,并将其传递给可组合函数。现在一切正常!
如果以后有人需要的话,这里是代码:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val wordViewModel by viewModels<WordViewModel> {
WordViewModelFactory((this.applicationContext as WordsApplication).repository)
}
setContent {
WordBookApp(wordViewModel)
}
}
}
@Composable
fun WordBookApp(wordViewModel: WordViewModel) {
val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())
...
我目前正在关注 Android Room with a View codelab 并尝试将其与 Jetpack Compose 一起采用。我卡在了在 compose 函数中初始化 viewModel。
我得到的错误:
None of the following functions can be called with the arguments supplied:
public inline fun <reified VM : ViewModel> viewModel(viewModelStoreOwner: ViewModelStoreOwner = ..., key: String? = ..., factory: ViewModelProvider.Factory? = ...): TypeVariable(VM) defined in androidx.lifecycle.viewmodel.compose
public fun <VM : ViewModel> viewModel(modelClass: Class<TypeVariable(VM)>, viewModelStoreOwner: ViewModelStoreOwner = ..., key: String? = ..., factory: ViewModelProvider.Factory? = ...): TypeVariable(VM) defined in androidx.lifecycle.viewmodel.compose
@Composable
fun WordBookApp() {
val context = LocalContext.current
val wordViewModel: WordViewModel by viewModel( // error here - viewModel
WordViewModelFactory((context.applicationContext as WordsApplication).repository)
)
val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())
...
视图模型和视图模型工厂:
class WordViewModel(private val repository: WordRepository) : ViewModel() {
val allWords: LiveData<List<Word>> = repository.allWords.asLiveData()
fun insert(word: Word) = viewModelScope.launch {
repository.insert(word)
}
}
class WordViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(WordViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return WordViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
其他部分代码:
class WordRepository(private val wordDao: WordDao) {
val allWords: Flow<List<Word>> = wordDao.getAlphabetizedWords()
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}
class WordsApplication : Application() {
private val database by lazy { WordRoomDatabase.getDatabase(this) }
val repository by lazy { WordRepository(database.wordDao()) }
}
@Database(entities = [Word::class], version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context): 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"
).build()
INSTANCE = instance
// return instance
instance
}
}
}
}
AndroidMenifest.xml
<application
android:name=".WordsApplication"
....
有人可以帮忙吗?谢谢!
既然你没有提到这个问题,我已经开始打字了。这可能与您的问题无关,但也许您仍然应该阅读此内容。
如果问题是视图模型中的值没有更新,- 在可组合的内部初始化视图模型,这是非常糟糕的主意。
您看到可组合项经常重新组合,重新执行其中的每一行代码。因此,如果您像这样初始化此可组合项内的视图模型,它将在每次重新组合时重新初始化。理论上,即使在帧速率下也可以进行重组(在许多情况下甚至可以)。因此,这不是在可组合项中声明变量的方法。
好的,remember
可组合项可以帮助您解决这个问题。如果用 remember
包装初始化语句,它不会在重组时重新初始化。但是,它有其局限性。例如,如果可组合项被销毁,例如,如果您将其滑出屏幕,remember
ed 值就会丢失。 remember
随着包含它的可组合项的销毁而被销毁。
因此,对于动画之类的小东西,可以将变量存储在可组合项中,但对于重要的东西,你不应该相信这个框架。
因此,最好的方法是在主 activity 中初始化视图模型,然后将其方法和变量传递给可组合项。您甚至可以传递视图模型本身,但大多数时候不需要。
代码:
@Composable
fun WordBookApp() {
val context = LocalContext.current
val wordViewModel: WordViewModel by remember {
viewModel( // error here - viewModel
WordViewModelFactory((context.applicationContext as WordsApplication).repository)
)
}
val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())
...
从@MARSK 的回答中得到了线索并修复了它。将视图模型的初始化移动到 MainActivity
的 onCreate()
,并将其传递给可组合函数。现在一切正常!
如果以后有人需要的话,这里是代码:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
val wordViewModel by viewModels<WordViewModel> {
WordViewModelFactory((this.applicationContext as WordsApplication).repository)
}
setContent {
WordBookApp(wordViewModel)
}
}
}
@Composable
fun WordBookApp(wordViewModel: WordViewModel) {
val words: List<Word> by wordViewModel.allWords.observeAsState(listOf())
...