我有一個使用 jetpack compose 建立的電影應用程式。我在其中展示了用戶選擇的最喜歡的電影。收藏夾應該完美地存在於使用者介面中,但是當我從使用者介面中刪除項目時,使用者介面不會立即重新組合以反映變更。怎麼辦?
這是我的使用者介面
@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)))} ) } }
這是我的視圖模型
@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() } }
當您查看 _favs
的寫入用法時,問題變得非常明顯:
只有 getfavmovies
會更新它。
雖然這聽起來像“你應該在刪除功能中更新你的用戶介面狀態”,但我想提出一個替代解決方案:
利用 room
flows
的力量!
首先在 viewmodel 中定義 loadingstate
,它將儲存為串流:
sealed class loadingstate { object loading : loadingstate() data class success : loadingstate() data class error(val error : string) : loadingstate() object initial : loadingstate() } private val loadingstate = mutablestateflow<loadingstate>(initial)
然後,更改 getfavmovies()
函數以使用此機制:
fun getfavmovies() { viewmodelscope.launch { try { _loadingstate.value = loadingstate.loading movierepoimpl.loadfavs() // a function which triggers e.g. a network call to actually load your movies _loadingstate.value = loadingstate.success } catch (ex: exception) { _loadingstate.value = loadingstate.error(ex.localizedmessage!!) } } }
最後,結合你的狀態和數據,讓奇蹟發生。一旦其中一個發生變化,這將自動更新:)
val favs: flow<uistates> = combine( movierepoimpl.getmovies(), loadingstate ) { movies, loadingstate -> when (loadingstate) { is loadingstate.loading -> uistates.loading is loadingstate.success -> uistates.success(movies) is loadingstate.error -> uistates.error(error) is loadingstate. initial -> uistates.initial } }
免責聲明:其中可能有拼字錯誤,我沒有方便的 ide
讓我們透過改進程式碼來結束這個答案:
避免將 =
用於不傳回任何內容的函數(getfavmovies
、insertmovie
、deletemovies
)。否則,你會例如期望 getfavmovies
返回電影列表
remember { mutablestateof(movieviewmodel.getfavmovies()) }
並沒有真正的 remember
任何東西,是嗎?這太惱人了。我建議將其移至 viewmodel 中: init { movieviewmodel.getfavmovies() }
val uistates = movieviewmodel.favs.collectasstate()
:命名表示這是多個 ui 狀態,請使用單數; uistates
類別本身也是如此。另外,如果這裡直接使用“.value”,可以跳過這裡的重新宣告:when(val currentstate = uistates.value)
您的 uistate 包含 mutablelist9fed9bb6bc0c66d7a13f33ab448de4c5
。對 ui 狀態使用可變值是一個壞主意,例如它可能會混淆您的 stateflow
。一定要避免這種情況。只需使用普通的舊列表:) https://www.php.cn/link/ff685590317f1330efc73f396ac92cd7
使用 collectasstatewithlifecycle()
而不是 collectasstate()
。請參閱https://medium.com/androiddevelopers/consuming-flows -安全地在jetpack-compose-cde014d0d5a3
您絕對不應該在 viewmodel 中存取 repoimpl
:將儲存庫拆分為介面和實作的整個想法是隱藏實作
定義一個screen-composable 是個好主意,它不需要viewmodel,只需要數據,類似於https://developer.android.com/jetpack/compose/state#state-hoisting:
.
@Composable private fun FavScreen( val viewModel: MovieViewModel, ) { FawScreen( uiState = viewModel.fav.collectAsStateWithLifecycle().value, shouldShowDialog = viewModel.shouldShowDialog.collectAsStateWithLifecycle().value ) } @Composable private fun FavScreen( val uiState: UiStates, val shouldShowDialog: Boolean ) { // your current ui }
以上是使用房間資料庫刪除 jetpack 中的所有項目後,UI 未重新組合的詳細內容。更多資訊請關注PHP中文網其他相關文章!