我有一个用Jetpack Compose构建的电影应用程序.有部分我正在播放用户 Select 的最受欢迎的电影.优点应该完美地出现在用户界面中,但当我从用户界面中删除项目时,用户界面不会立即重组以反映更改.该怎么办呢?

这是我的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)))}
        )
    }
}

这是我的取景模型

@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


让我们通过改进您的代码来结束这个答案:

  1. 对于不返回任何内容的函数(getFavMoviesinsertMoviedeleteMovies),避免使用=.否则,您可能会期望getFavMovies返回一个电影列表

  2. remember { mutableStateOf(movieViewModel.getFavMovies()) }不等于remember,不是吗?这太令人恼火了.我建议将其移到视图Model:init { movieViewModel.getFavMovies() }

  3. val uiStates = movieViewModel.favs.collectAsState():命名意味着这是多个UI状态,请使用单数;UiStates类本身也是如此.此外,如果您在此处直接使用".Value",则可以跳过此处的重新声明:when(val currentState = uiStates.value)

  4. 您的UiState包含MutableList<FavModel>.使用UI状态的可变值不是一个好主意,例如,它可能会使您的StateFlow感到困惑.一定要避免这种情况.只需使用一个简单的老式列表:)https://stackoverflow.com/a/72099532/12871582

  5. 使用collectAsStateWithLifecycle()而不是collectAsState().见https://medium.com/androiddevelopers/consuming-flows-safely-in-jetpack-compose-cde014d0d5a3

  6. 您绝对不应该在viewModel中访问RepoImpl:将repo拆分为接口和实现背后的全部思想是隐藏实现

  7. 定义一个不需要view Model而只需要数据的Screen-Composable是个好主意,类似于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
}

Java相关问答推荐

如何计算内循环的时间复杂度?

Java 22模式匹配不适用于记录模式匹配.给出汇编问题

查找最大子数组的和

如何在SystemiccationRetryListenerSupport中获得类级别的spring retryable annotation中指定的标签?

XPages-在第二次点击按钮之前延迟

存根基类的受保护方法

连接Quarkus中的两个异步操作

名称冲突具有相同的擦除

对于亚洲/香港,使用ResolverStyle.STRICT的LocalDate.parse返回意外结果

类型集合的Jackson JsonNode:类型引用的对象读取器应该是Singleton吗?

Java SSLKeyManager出厂密码

Lombok@Nonnull是否也对供应商有影响?

try 使用Spring集成和MySQL实现发件箱模式时,锁定等待超时

Oj算法 MatrixR032从字符串、归一化和余弦相似度计算创建

AWS Java SDK v2.x中没有setObjectAcl方法

如何在Record Java中使用isRecord()和RecordComponent[]?

无限递归Java问题

字符串的Gzip压缩在java11和java17中给出了不同的结果

读取ConcurrentHashMap中的可变对象

在不带instanceof或switch的java中记录模式