看过一些关于类似问题的帖子,但没有任何东西能让我的代码工作。
我有一个存储大约 100 个对象的数据库。这些显示在屏幕上,但一旦我通过按下按钮更新数据库中的其中一个对象,数据库就会更改值(在应用程序检查器中),但不会反映在 UI 中。另外,此更改只能完成一次,之后它不会让我再次更新数据库中的同一对象。
@Composable
fun MainScreen(navController: NavController, cocktailViewModel: CocktailViewModel) {
val allCocktails = cocktailViewModel.cocktailsFromDb.observeAsState(emptyList())
LaunchedEffect(Unit) {
cocktailViewModel.getAllCocktails()
}
LazyColumn(
modifier = Modifier
.padding(bottom = 110.dp),
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp
),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(allCocktails.value) { drink ->
Row(
modifier = Modifier
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Text(text = drink.strDrink)
Icon(
imageVector = if (drink.isBookmarked) Icons.Filled.Bookmark else Icons.Filled.BookmarkBorder,
contentDescription = "Bookmark",
tint = Color.Black,
modifier = Modifier
.size(40.dp)
.clickable {
cocktailViewModel.toggleBookmark(drink) // Here is the update
}
)
}
HorizontalDivider(
modifier = Modifier.padding(vertical = 4.dp),
thickness = 1.dp,
color = Color.Gray
)
}
}
}
这就是视图,她是视图模型的一部分:
class CocktailViewModel(private val cocktailDao: CocktailDAO) : ViewModel() {
private val _cocktailsFromDb = MutableLiveData<List<ModifiedDrink>>()
val cocktailsFromDb: LiveData<List<ModifiedDrink>> = _cocktailsFromDb
fun toggleBookmark(drink: ModifiedDrink) {
viewModelScope.launch(Dispatchers.IO) {
try {
val updatedDrink = drink.copy(isBookmarked = !drink.isBookmarked)
cocktailDao.updateDrink(updatedDrink)
Log.d("CocktailViewModel", "Successfully updated drink: ${updatedDrink.strDrink}")
// Just for logging purposes
val fetchedDrink = cocktailDao.getDrinkById(updatedDrink.idDrink)
Log.d("CocktailViewModel", "Fetched drink by ID after update: $fetchedDrink")
} catch (e: Exception) {
Log.e("CocktailViewModel", "Error during toggleBookmark: ${e.message}", e)
}
}
}
fun getAllCocktails() {
viewModelScope.launch {
try {
val drinks = withContext(Dispatchers.IO) {
cocktailDao.getAllDrinks()
}
_cocktailsFromDb.postValue(drinks)
} catch (e: Exception) {
Log.e("CocktailViewModel", "Error fetching drinks from database: ${e.message}", e)
}
}
}
}
我的DAO:
@Dao
interface CocktailDAO {
@Query("SELECT * FROM Cocktails")
fun getAllDrinks(): Flow<List<ModifiedDrink>>
@Query("SELECT * FROM Cocktails WHERE idDrink = :id")
suspend fun getDrinkById(id: Int): ModifiedDrink
@Update
suspend fun updateDrink(drink: ModifiedDrink)
}
在我看来,在视图模型中更改cocktailsFromDb 中的饮料应该会创建 UI 更新,因为我在视图中观察到它。然而,这似乎不起作用。
“[...] 在视图模型中更改cocktailsFromDb 中的饮料应该会创建 UI 更新”。
是的。唯一的问题是你永远不会做更改
cocktailsFromDb
中的任何内容。您唯一一次实际上使用_cocktailsFromDb.postValue(drinks)
更改LiveData是在getAllCocktails()
中。而且这只被调用一次,即在 MainScreen 的 LaunchedEffect 中(请注意,您传递了 Unit
作为 LaunchedEffect 的键,这将有效地防止再次执行)。
您真正想做的是将 LiveData 全部删除(当您使用 Compose 时,LiveData 已过时)并用 Flow 替换它。实际上,你甚至应该使用 Flow 作为 Dao 中的返回值:
fun getAllDrinks(): Flow<List<ModifiedDrink>>
现在您甚至不再需要专用的
getAllCocktails()
函数,因为每当数据库中发生更改时,流程都会自动提供新的鸡尾酒列表。
相反,你的视图模型应该只需要这个属性:
val cocktails = cocktailDao.getAllDrinks()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
stateIn
将流转换为 StateFlow,其语义与 LiveData 相似,因为可以观察其变化。 与您的 LiveData 不同,此流程现在将在数据库中发生任何更改时自动更新。
在可组合项中,您现在可以将流程转换为 State 对象,如下所示:
val allCocktails = cocktailViewModel.cocktails.collectAsStateWithLifecycle()
为此,您需要 gradle 依赖项
androidx.lifecycle:lifecycle-runtime-compose
。
由于不再有
getAllCocktails()
,您现在也可以删除 LaunchedEffect。
更新数据库的问题可能是由 UI 仍然具有之前的
ModifiedDrink
对象引起的。因此,您将使用相同的对象重复调用 toggleBookmark
,从而导致相同的数据库更新 - 其中只有第一个实际上会更改任何内容。当你的 UI 现在正确更新时,这个问题也应该得到解决。