8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

我可以在 MutableLiveData 上使用 observerAsState() 吗?

thisismy-stackoverflow 1月前

18 0

我在构建一个小型电影库应用程序时遇到了一个问题。我的结构如下:我有一个 MovieSearchScreen 函数,它调用一个 SearchBar 函数以及一个 MovieSearchR...

我在构建一个小型电影库应用程序时遇到了一个问题。我的结构如下:我有一个 MovieSearchScreen 函数,它调用一个 SearchBar 函数和一个 MovieSearchResults 函数。在 Searchbar 函数中,我有一个 TextField、一个 leadingIcon 和一个 trailingIcon,后者有一个可点击修饰符,它调用我的 viewModel 中的 updateMovieResource 函数:

    @Composable
    fun Create(navController: NavHostController, viewModel: MovieViewModel) {
        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(color = Color.DarkGray)
        ) {
            SearchBar(viewModel = viewModel)
            MovieSearchResults(navController = navController)
        }
    }

    @Composable
    private fun SearchBar(viewModel: MovieViewModel) {
        var userInput: String by remember {
            mutableStateOf("")
        }
        TextField(
            value = userInput,
            onValueChange = {
                userInput = it
                            },
            modifier = Modifier
                .fillMaxWidth(),
            colors = TextFieldDefaults.colors(
                focusedContainerColor = Color.Gray,
                unfocusedContainerColor = Color.Gray,
                focusedTextColor = Color.White,
                unfocusedTextColor = Color.White
            ),
            placeholder = {
                Text(
                    text = stringResource(id = R.string.search_bar),
                    color = Color.White
                )
                          },
            leadingIcon = {
                Icon(
                    imageVector = Icons.Rounded.Clear,
                    contentDescription = "Clear",
                    modifier = Modifier
                        .clickable {
                            userInput = ""
                                   },
                    tint = Color.White
                )
                          },
            trailingIcon = {
                Icon(
                    imageVector = Icons.Rounded.Search,
                    contentDescription = "Search",
                    tint = Color.White,
                    modifier = Modifier.clickable {
                        if (userInput.isNotBlank()) {
                            viewModel.updateMovieResource(userInput)
                        }
                    }
                )
            }
        )
    }

在这个 viewModel 中,我有几个值,现在重要的是我的 _movieRepository,我从中管理我的 api 调用,_movieResource,movieResource 和 updateMovieResource 方法:

class MovieViewModel: ViewModel() {

    private lateinit var _selectedMovie: MutableState<Movie>

    var selectedMovie: Movie
        get() = _selectedMovie.value
        set(movie){
            _selectedMovie.value = movie
        }

    private val _movieRepository: MovieRepository = MovieRepository()

    val movieResource: LiveData<Resource<ResponseResult>>
        get() = _movieResource

    private val _movieResource: MutableLiveData<Resource<ResponseResult>> =
        MutableLiveData(Resource.Empty())

    fun updateMovieResource(movie: String) {
        _movieResource.value = Resource.Loading()

        viewModelScope.launch {
            _movieResource.value = _movieRepository.getMovie(movie)
        }
    }
}

我使用 movieResource 来确定我的 api 调用是否成功、仍在加载或返回错误,以便我知道要显示什么。我在这里的 MovieSearchResults 可组合中使用了这个 movieResource:

@Composable
    private fun MovieSearchResults(
        navController: NavHostController,
        viewModel: MovieViewModel = androidx.lifecycle.viewmodel.compose.viewModel()
    ){
        val movieResource: Resource<ResponseResult>? by viewModel.movieResource.observeAsState()
        
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            when(movieResource) {
                is Resource.Success ->
                    LazyVerticalGrid(
                        columns = GridCells.Fixed(3)
                    ) {
                        items(
                            items = (movieResource as Resource.Success<ResponseResult>).data!!.results,
                            itemContent = {
                                AsyncImage(
                                    model = it.backdropPath,
                                    contentDescription = it.title,
                                    modifier = Modifier.clickable {
                                        viewModel.selectedMovie = it
                                        navController.navigate(MovieLibraryScreens.MovieDetailsScreen.name)
                                    }
                                )
                            }
                        )
                    }
                is Resource.Error ->
                    Text(
                        text = (movieResource as Resource.Error<ResponseResult>).message!!,
                        fontSize = 30.sp,
                        color = Color.Red
                    )
                is Resource.Empty ->
                    Text(
                        text = "Search for a movie!",
                        fontSize = 30.sp,
                        color = Color.White
                    )
                is Resource.Loading ->
                    Text(
                        text = "Loading...",
                        fontSize = 30.sp,
                        color = Color.White
                    )
                else ->
                    Text(
                        text = "Something went wrong...",
                        fontSize = 30.sp,
                        color = Color.White
                    )
            }
        }
    }

我从这里的存储库中检索这些数据:

class MovieRepository {

    private val _apiService: ApiService = Api().createApi()

    private val _apiKey: String = API_KEY

    suspend fun getMovie(movie: String): Resource<ResponseResult> {
        val response = try {
            withTimeout(5_000) {
                _apiService.getMovie(movie, _apiKey)
            }
        } catch (e: Exception) {
            Log.e("MovieRepository", e.message ?: "No exception message available")
            return Resource.Error("An unknown error occurred while fetching data for the movie: $movie")
        }
        Log.d("Result", response.results.toString())
        return Resource.Success(response)
    }
}

当我 Log.d 我的 response.results 时,它会记录我从 api 调用的正确数据,但当我返回 Resource.Success(response) 时。viewModel 中的 _movieResource 似乎没有更新为 Resource.Success(response)。此外,回到我的 MovieSearchResults 可组合项中,当调用 updateMovieResource 方法时,when 语句似乎没有注意到 _movieResource 在开始时已更新为 Resource.Loading()。我完全被这个问题困住了,感觉我对 MutableLiveData 做了一些错误的事情,但我并不完全确定。请帮帮我!

我记录了返回预期 api 响应的结果以及我的 movieResource,当 updateMovieResource 完成运行时,最终返回 Resource.Empty()。此后,我尝试将 MutableLiveData 更改为标准 mutableStateOf(),但这是不可能的,因为我有多个不同的资源响应。

帖子版权声明 1、本帖标题:我可以在 MutableLiveData 上使用 observerAsState() 吗?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由thisismy-stackoverflow在本站《kotlin》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 虽然您显然已经解决了当前问题,但我想更深入地解释一下问题所在。

    但首先,您应该更新视图模型。您仍在使用 LiveData (这是 Compose 时代之前的遗留,现已过时)和 MutableState ,它们仅应在可组合项中使用。两者都需要替换为 Flows:

    class MovieViewModel : ViewModel() {
        private val _selectedMovie = MutableStateFlow<Movie?>(null)
        val selectedMovie = _selectedMovie.asStateFlow()
    
        private val _movieRepository: MovieRepository = MovieRepository()
    
        private val _movieResource = MutableStateFlow<Resource<ResponseResult>>(Resource.Empty())
        val movieResource: StateFlow<Resource<ResponseResult>> = _movieResource.asStateFlow()
    
        fun updateMovieResource(movie: String) {
            _movieResource.value = Resource.Loading()
    
            viewModelScope.launch {
                _movieResource.value = _movieRepository.getMovie(movie)
            }
        }
    
        fun selectMovie(movie: Movie) {
            _selectedMovie.value = movie
        }
    }
    

    您不需要调用 observeAsState collectAsStateWithLifecycle() 组合项来将流转换为状态对象。


    解决了这个问题,回到最初的问题。 MovieSearchResults 有一个 viewModel 参数。罪魁祸首是默认值 viewModels() :这将创建实例,因此实际上涉及两个视图模型:第一个由使用 SearchBar ,第二个由使用 MovieSearchResults 。只有第一个从存储库中检索数据,因此第二个将始终为空。

    要解决这个问题,您可以将传递给 SearchBar 的相同视图模型实例传递给 MovieSearchResults。但实际上,您 永远不应 将视图模型传递给可组合项。相反,在最顶层的可组合项中检索需要视图模型的实例(似乎 Create 在您的例子中是这样的),然后仅将视图模型的 状态 回调 给其他可组合项:

    @Composable
    fun Create(navController: NavHostController) {
        val viewModel: MovieViewModel = viewModel()
        val movieResource: Resource<ResponseResult> by viewModel.movieResource.collectAsStateWithLifecycle()
        
        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(color = Color.DarkGray),
        ) {
            SearchBar(
                updateMovieResource = viewModel::updateMovieResource,
            )
    
            MovieSearchResults(
                navController = navController,
                movieResource = movieResource,
                selectMovie = viewModel::selectMovie,
            )
        }
    }
    

    SearchBar 和 MovieSearchResults 声明如下:

    @Composable
    private fun SearchBar(
        updateMovieResource: (String) -> Unit,
    ) { /*...*/ }
    
    @Composable
    private fun MovieSearchResults(
        navController: NavHostController,
        movieResource: Resource<ResponseResult>,
        selectMovie: (Movie) -> Unit,
    ) { /*...*/ }
    

    这样,您的可组合项就独立于视图模型,对于重组更加稳健(因为视图模型不稳定,更易于测试(因为您不需要视图模型,您可以提供简单的虚拟参数),可以更容易地重用并可以更好地预览(@Preview)。

    它还解决了您遇到的问题。

返回
作者最近主题: