我有一个使用最新依赖项和带有 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())
}
}
问题是由这段代码引起的(如何替换 Image 可组合项,以便即使它一开始将可绘制对象设置为 null,它也会等待?):
@Composable
fun Hero(recipePreview: RecipeModel) {
val context = LocalContext.current
val imageName = recipePreview.image.replace(".jpg", "")
val drawableId = remember(imageName) {
context.resources.getIdentifier(
imageName,
"drawable",
context.packageName
)
}
Box(Modifier.height(280.dp)) {
Image(
painter = painterResource(id = drawableId),
contentDescription = recipePreview.title,
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
contentScale = ContentScale.FillBounds,
)
}
}
当我删除图像时,食谱的其余部分就会出现。所以只有图像是问题,因为一开始我有空。
请帮忙。预先感谢。
这可能不是唯一的问题,但将
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 }
}