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

