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

网络响应处理异常:Error状态无法触发问题求助

问题:Paging3网络请求Error状态无法触发

Loading和Success状态可正常工作,但Error状态无法触发。模拟错误场景时,触发的是Loading和Success状态,而非Error状态。

相关代码如下:

Repository类

fun getMoviesStream(genreId: String?, sortBy: String): Flow<PagingData<Result>> {
    return Pager(
        config = PagingConfig(pageSize = MOVIES_PAGE_SIZE, enablePlaceholders = false),
        pagingSourceFactory = { MoviesPagingSource(api, genreId, sortBy) }
    ).flow
}

ViewModel类

private val _moviesResponse = MutableStateFlow<Resource<PagingData<Result>>>(Resource.Loading())
val moviesResponse = _moviesResponse.asStateFlow()

fun getMovies(genreId: String?, sortBy: String) {
    viewModelScope.launch {
        _moviesResponse.value = Resource.Loading()
        moviesRepository.getMoviesStream(genreId, sortBy).cachedIn(viewModelScope)
            .catch { throwable ->
                _moviesResponse.value = Resource.Error(
                    throwable.localizedMessage ?: "An unexpected error occurred"
                )
            }
            .collect { result ->
                delay(500)
                _moviesResponse.value = Resource.Success(result)
            }
    }
}

Fragment类

private fun requestMoviesData(genreId: String?, sortBy: String) {
    viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            moviesViewModel.getMovies(genreId, sortBy)
            moviesViewModel.moviesResponse.collect { response ->
                when (response) {
                    is Resource.Error -> {
                        Toast.makeText(
                            requireContext(),
                            response.message,
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                    is Resource.Loading -> {
                        binding.spinnerLoading.visibility = View.VISIBLE
                    }
                    is Resource.Success -> {
                        binding.spinnerLoading.visibility = View.GONE
                        response.data?.let { moviesAdapter.submitData(it) }
                    }
                }
            }
        }
    }
}

问题原因

  1. Paging3的Flow特性:Pager.flow不会直接抛出异常,网络错误会被封装到PagingData的LoadState中,不会触发catch操作符。
  2. 逻辑覆盖:当前collect块会在任何情况下(包括错误场景返回空PagingData)执行,覆盖了catch中设置的Error状态。
  3. cachedIn位置错误:放在catch之后会导致异常被缓存流程吞掉,无法被捕获。

修复方案

1. 修改ViewModel的状态处理逻辑

监听PagingData的loadStateFlow来捕获错误,替换原有的catch逻辑:

private val _moviesResponse = MutableStateFlow<Resource<PagingData<Result>>>(Resource.Loading())
val moviesResponse = _moviesResponse.asStateFlow()

fun getMovies(genreId: String?, sortBy: String) {
    viewModelScope.launch {
        _moviesResponse.value = Resource.Loading()
        moviesRepository.getMoviesStream(genreId, sortBy)
            .cachedIn(viewModelScope)
            .collectLatest { pagingData ->
                _moviesResponse.value = Resource.Success(pagingData)
                // 监听加载状态,捕获错误
                pagingData.loadStateFlow.collect { loadStates ->
                    val refreshError = loadStates.refresh as? LoadState.Error
                    refreshError?.let {
                        _moviesResponse.value = Resource.Error(
                            it.error.localizedMessage ?: "An unexpected error occurred"
                        )
                    }
                }
            }
    }
}

2. 确保PagingSource正确抛出异常

检查MoviesPagingSource的load方法,网络请求失败时必须返回LoadResult.Error,而非空数据:

class MoviesPagingSource(private val api: MovieApi, ...) : PagingSource<Int, Result>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Result> {
        return try {
            val currentPage = params.key ?: 1
            val response = api.getMovies(genreId, sortBy, currentPage)
            
            if (!response.isSuccessful) {
                throw IOException("Request failed with code ${response.code()}")
            }
            
            val movies = response.body()?.results ?: emptyList()
            LoadResult.Page(
                data = movies,
                prevKey = if (currentPage == 1) null else currentPage - 1,
                nextKey = if (movies.isEmpty()) null else currentPage + 1
            )
        } catch (e: Exception) {
            // 将异常封装为LoadResult.Error
            LoadResult.Error(e)
        }
    }
}

3. 可选:优化Fragment的分页错误处理

如果需要处理分页加载时的错误,可以监听Adapter的loadStateFlow:

private fun requestMoviesData(genreId: String?, sortBy: String) {
    viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
            moviesViewModel.getMovies(genreId, sortBy)
            moviesViewModel.moviesResponse.collect { response ->
                when (response) {
                    is Resource.Error -> {
                        Toast.makeText(requireContext(), response.message, Toast.LENGTH_SHORT).show()
                        binding.spinnerLoading.visibility = View.GONE
                    }
                    is Resource.Loading -> {
                        binding.spinnerLoading.visibility = View.VISIBLE
                    }
                    is Resource.Success -> {
                        binding.spinnerLoading.visibility = View.GONE
                        response.data?.let { moviesAdapter.submitData(it) }
                        // 监听分页加载错误
                        moviesAdapter.loadStateFlow.collect { loadStates ->
                            val appendError = loadStates.append as? LoadState.Error
                            appendError?.let {
                                Toast.makeText(requireContext(), it.error.localizedMessage, Toast.LENGTH_SHORT).show()
                            }
                        }
                    }
                }
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 14:10:21