Jetpack compose Room 数据库 UI 更新问题

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

看过一些关于类似问题的帖子,但没有任何东西能让我的代码工作。

我有一个存储大约 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 更新,因为我在视图中观察到它。然而,这似乎不起作用。

android kotlin android-jetpack-compose android-room android-livedata
1个回答
0
投票

[...] 在视图模型中更改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 现在正确更新时,这个问题也应该得到解决。

© www.soinside.com 2019 - 2024. All rights reserved.