Android 应用程序的 ViewModel getSingle(id) 返回 null(Kotlin Room 数据库)

问题描述 投票:0回答:1

我有一个使用最新依赖项和带有 Kotlin DSL 的 Gradle 8.3 的应用程序。 ViewModel 工作正常,可以从 Room 本地 SQLite 数据库检索所有数据。但不适用于通过 id 检索单个食谱。该应用程序编译没有错误。使用 Logcat 发现问题。

这里是与该项目相关的不同 Kotlin 文件的代码。

食谱数据库.kt

@Database(
entities = [RecipeModel::class],
version = Constants.version,
exportSchema = false
)
abstract class RecipeDatabase: RoomDatabase() {

    abstract fun recipeDao(): RecipeDAO

    companion object{

        @Volatile
        private var INSTANCE: RecipeDatabase? = null

        fun getDatabase(
            context: Context
        ): RecipeDatabase{
            val tempInstance = INSTANCE
            if(tempInstance != null){
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(context, RecipeDatabase::class.java, Constants.database)
                    .createFromAsset(Constants.databaseLocation)
                    .fallbackToDestructiveMigration()
                    .build()
                INSTANCE = instance
                return instance
            }
        }
    }
}

RecipeDAO.kt

@Dao
interface RecipeDAO {

    @Query("SELECT * FROM recipes ORDER BY id ASC")
    fun readAllData(): LiveData<List<RecipeModel>> // THIS WORKS

    @Query("SELECT * FROM recipes WHERE id=:id")
    fun loadSingle(id: String): LiveData<RecipeModel> // THIS DOESNT

}

RecipeRepository.kt

class RecipeRepository(private val recipeDAO: RecipeDAO) {

val readAllData: LiveData<List<RecipeModel>> = recipeDAO.readAllData() // THIS WORKS

fun loadSingle(recipeId: String): LiveData<RecipeModel> {
    return recipeDAO.loadSingle(recipeId)
}

}

食谱视图模型

 class RecipeViewModel(application: Application): AndroidViewModel(application) {

    val readAllData: LiveData<List<RecipeModel>>
    private val repository: RecipeRepository
    lateinit var eventSingle: LiveData<RecipeModel>

    init {
        val recipeDAO = RecipeDatabase.getDatabase(application).recipeDao()
        repository = RecipeRepository(recipeDAO)
        readAllData = repository.readAllData // THIS WORKS
    }

    fun getSingle(recipeId: String) { // THIS DOESNT
        eventSingle = repository.loadSingle(recipeId)
    }

}

可组合 RecipeScreen.kt

@Composable
fun RecipeScreen(navController: NavHostController, recipeViewModel: RecipeViewModel, recipeID: Int) {
    println("RECIPE ID" + recipeID.toString()) // THIS WORKS
    val recipeAvailable = recipeViewModel.getSingle(recipeID.toString()) // THIS DOESNT
    val available by recipeViewModel.eventSingle.observeAsState() // THIS DOESNT
    println(available) // <-- null
} 

NavHost 可组合

composable(route = Recipe.route) {
            backStackEntry ->
        val recipeID = backStackEntry.arguments?.getString("recipeID")
        if (recipeID != "") {
            RecipeScreen(navController, recipeViewModel, recipeID!!.toInt())
        }
    }

请帮忙。预先感谢。

android kotlin repository android-room android-viewmodel
1个回答
0
投票

这可能不是唯一的问题,但将

var
与 LiveData 结合会导致问题。 UI层开始观察第一个LiveData。当您更改属性以指向不同的 LiveData 时,UI 层并不知道,只会继续观察第一个 LiveData。

要解决此问题,您可以创建所请求 ID 的后备 LiveData,然后

switchMap
生成所需的单个数据库行的最终映射。

更新了 ViewModel 代码:

class RecipeViewModel(application: Application): AndroidViewModel(application) {

    val readAllData: LiveData<List<RecipeModel>>
    private val repository: RecipeRepository
    private val eventSingleId = MutableLiveData<String>()
    val eventSingle: LiveData<RecipeModel> =
        eventSingleId.switchMap { repository.loadSingle(it) }

    init {
        val recipeDAO = RecipeDatabase.getDatabase(application).recipeDao()
        repository = RecipeRepository(recipeDAO)
        readAllData = repository.readAllData
    }

    fun getSingle(recipeId: String) {
        eventSingleId.value = recipeId
    }

}

顺便说一句,您对数据库的单例访问很容易受到实例化两个数据库的影响。它需要是双重检查锁。更新您的代码以对其是否已在同步块内实例化进行第二次检查:

fun getDatabase(
    context: Context
): RecipeDatabase{
    var instance = INSTANCE
    if(instance != null){
        return tempInstance
    }
    synchronized(this) {
        instance = INSTANCE
        if(instance != null){
            return instance
        }
        instance = Room.databaseBuilder(context, RecipeDatabase::class.java, Constants.database)
            .createFromAsset(Constants.databaseLocation)
            .fallbackToDestructiveMigration()
            .build()
        INSTANCE = instance
        return instance
    }
}

或者使用 Elvis 运算符和

also
:

来简化代码
fun getDatabase(
    context: Context
): RecipeDatabase =
    INSTANCE ?: synchronized(this) {
        INSTANCE ?: Room.databaseBuilder(context, RecipeDatabase::class.java, Constants.database)
            .createFromAsset(Constants.databaseLocation)
            .fallbackToDestructiveMigration()
            .build()
            .also { INSTANCE = it }
    }
© www.soinside.com 2019 - 2024. All rights reserved.