You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose+Room删除全部收藏后UI未重组的解决方法

问题

我基于Jetpack Compose开发了一款电影应用,其中包含展示用户所选收藏电影的模块。收藏电影可正常显示,但当从UI中删除全部收藏项后,UI并未立即重组以更新显示状态。

UI实现代码

@Composable
fun FavScreen(
    movieViewModel: MovieViewModel
) {

    remember { mutableStateOf(movieViewModel.getFavMovies()) }
    val shouldShowDialog = remember { mutableStateOf(false) }
    val uiStates = movieViewModel.favs.collectAsState()


    Column {
        Row {
            Text(
                text = "Fav Movies",
                fontSize = 25.sp,
                fontFamily = FontFamily(Font(R.font.nsb)),
                modifier = Modifier.padding(20.dp).weight(1f)
            )

            IconButton(onClick = {
                shouldShowDialog.value  = true
            }) {
                Icon(Icons.Filled.Delete, contentDescription = "")
            }
        }
        when(val currentState = uiStates.value){
            is MovieViewModel.UiStates.LOADING -> {
                Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                    CircularProgressIndicator()
                }
            }
            is MovieViewModel.UiStates.FAVS -> {
                val data = currentState.data
                if(data.isEmpty()){
                    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                        Text(
                            text = "No Fav Movies",
                            fontSize = 25.sp,
                            fontFamily = FontFamily(Font(R.font.nsb)),
                            modifier = Modifier.padding(20.dp))
                    }
                }
                else {
                    LazyColumn(){
                        items(data){ fav ->
                            Row(modifier = Modifier
                                .padding(20.dp)
                                .fillMaxWidth()
                                .clip(RoundedCornerShape(16.dp))
                                .background(color = Color.DarkGray)) {
                                AsyncImage(
                                    model = Utils.IMAGE_URL + fav.imageUrl,
                                    contentDescription = "",
                                    contentScale = ContentScale.Crop,
                                    filterQuality = FilterQuality.High,
                                    modifier = Modifier
                                        .width(150.dp)
                                        .height(150.dp))

                                Column(modifier = Modifier.padding(20.dp)) {
                                    Text(
                                        text = fav.title!!,
                                        fontSize = 20.sp,
                                        fontFamily = FontFamily(Font(R.font.nsb)))
                                    Text(
                                        text = fav.rating.toString() + "/10 IMDB",
                                        fontSize = 15.sp,
                                        fontFamily = FontFamily(Font(R.font.nsb)))
                                    Text(
                                        text = "Movie ID : #" + fav.movieID,
                                        fontSize = 15.sp,
                                        fontFamily = FontFamily(Font(R.font.nsb)))
                                }
                            }
                        }
                    }
                }
            }
            is MovieViewModel.UiStates.ERROR -> {
                Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
                    Text(
                        text = "No Fav Movies",
                        fontSize = 25.sp,
                        fontFamily = FontFamily(Font(R.font.nsb)),
                        modifier = Modifier.padding(20.dp))
                }
            }
            else -> {}
        }
    }
    if (shouldShowDialog.value){
        AlertDialog(
            onDismissRequest = { shouldShowDialog.value = false },
            confirmButton = {
                TextButton(onClick = {
                    movieViewModel.deleteMovies()
                    shouldShowDialog.value = false
                }) {
                    Text(text = "Proceed", fontFamily = FontFamily(Font(R.font.nsm)))
                }
            },
            dismissButton = {
                TextButton(onClick = {
                    shouldShowDialog.value = false
                }) {
                    Text(text = "Cancel", fontFamily = FontFamily(Font(R.font.nsm)))
                }
            },
            shape = RoundedCornerShape(16.dp),
            text = { Text(text = "Favs Deletion",fontFamily = FontFamily(Font(R.font.nsb)))},
            title = { Text(text = "Do you want to delete all fav movies ?", fontFamily = FontFamily(Font(R.font.nsb)))}
        )
    }
}

ViewModel代码

@HiltViewModel
class MovieViewModel @Inject constructor(
    private val movieRepoImpl: MovieRepoImpl
) : ViewModel() {
    

    private val _favs : MutableStateFlow<UiStates> = MutableStateFlow(UiStates.INITIAL)
    val favs get() = _favs.asStateFlow()


    // MOVIE DAO
    fun getFavMovies() = viewModelScope.launch {
        try {
            _favs.value = UiStates.LOADING
            movieRepoImpl.getFavs().collectLatest {
                _favs.value = UiStates.FAVS(it)
            }
        } catch (ex : Exception){
            _favs.value = UiStates.ERROR(ex.localizedMessage!!)
        }

    }
    fun insertMovie(favModel: FavModel) = viewModelScope.launch {
        movieRepoImpl.insertFav(favModel)
    }
    fun deleteMovies() = viewModelScope.launch {
        movieRepoImpl.deleteAllMovies()
    }

     sealed class UiStates {
         object LOADING : UiStates()
         data class FAVS(val data : MutableList<FavModel>) : UiStates()
         data class ERROR(val error : String) : UiStates()
         object INITIAL : UiStates()
     }
}
解决方案

1. 修复UI中错误的初始化逻辑

你当前UI里的这行代码完全无效,还可能导致初始化逻辑异常:

remember { mutableStateOf(movieViewModel.getFavMovies()) }

getFavMovies是启动协程监听收藏数据的函数,不能放在remember的mutableStateOf里。需要替换为LaunchedEffect,确保屏幕进入组合时自动启动监听:

@Composable
fun FavScreen(
    movieViewModel: MovieViewModel
) {
    // 替换原有的错误代码
    LaunchedEffect(Unit) {
        movieViewModel.getFavMovies()
    }
    val shouldShowDialog = remember { mutableStateOf(false) }
    val uiStates = movieViewModel.favs.collectAsState()
    
    // 剩余UI代码保持不变...
}

LaunchedEffect(Unit)会在Composable首次组合时执行一次,启动协程监听收藏数据的变化。

2. 确保Repository的getFavs返回可观察的Flow

如果你的movieRepoImpl.getFavs()是基于Room的DAO查询,确保返回的是Flow<List<FavModel>>,比如Room DAO中的方法定义:

@Dao
interface FavDao {
    @Query("SELECT * FROM fav_movies")
    fun getFavs(): Flow<List<FavModel>>
}

Room会在数据库数据变化时自动向Flow发射新的列表,这样ViewModel中的collectLatest就能收到更新,进而更新_favs StateFlow,触发UI重组。

3. 改用不可变List避免状态感知问题

ViewModel中UiStates.FAVS使用MutableList存在风险,Compose依赖状态的不可变性来感知变化,建议改为不可变的List:

sealed class UiStates {
    object LOADING : UiStates()
    data class FAVS(val data : List<FavModel>) : UiStates() // 把MutableList改成List
    data class ERROR(val error : String) : UiStates()
    object INITIAL : UiStates()
}

同时确保Repository返回的是不可变List(Room默认返回的就是不可变List),这样每次数据变化都会生成新的List实例,Compose能准确感知到状态更新。

完成以上修改后,删除全部收藏项时,Room会发射空列表,ViewModel的_favs会更新为FAVS(emptyList()),UI会自动重组显示"No Fav Movies"的提示。

内容的提问来源于stack exchange,提问作者Taki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 06:05:55