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

如何处理基于LiveData的Kotlin Jetpack Paging 3异常?

实现方案(仅使用LiveData,无需Flow)

1 首先修正现有代码的问题

你当前代码有两处可优化点:

  • ViewModel 中 getAllGitRepos 用 suspend 修饰没有必要,Pager.liveData 本身是异步回调的冷LiveData,直接暴露即可,不需要挂起
  • Activity 中把 observe 放到 lifecycleScope.launch 里是多余操作,observe 本身就是生命周期感知的异步回调

2 核心实现逻辑

Paging的加载异常会封装在 LoadState 中,我们通过PagingAdapter的状态回调捕获异常,再通过ViewModel的LiveData透传到UI层展示即可。

2.1 可选优化:Resource类改成密封类(更安全)

原来的data class+枚举的写法容易漏状态,改成密封类后编译器会强制检查所有分支:

sealed class Resource<out T> {
    object Loading : Resource<Nothing>()
    data class Success<out T>(val data: T) : Resource<T>()
    data class Error(val message: String, val throwable: Throwable? = null) : Resource<Nothing>()
}

如果要保留你原来的Resource类也可以正常使用。

2.2 新增一次性事件包装类(避免重复触发错误)

解决LiveData粘性事件导致旋转屏幕后重复弹出错误的问题:

open class Event<out T>(private val content: T) {
    var hasBeenHandled = false
        private set

    // 仅未消费过的事件会返回内容
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) null else {
            hasBeenHandled = true
            content
        }
    }
}

2.3 修改GitRepoPagingSource捕获异常

class GitRepoPagingSource(private val service: GitRepoApi) : PagingSource<Int, GitRepo>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, GitRepo> {
        return try {
            val currentPage = params.key ?: 1
            val response = service.getRepos(currentPage, params.loadSize)
            LoadResult.Page(
                data = response.items,
                prevKey = if (currentPage == 1) null else currentPage - 1,
                nextKey = if (response.items.isEmpty()) null else currentPage + 1
            )
        } catch (e: Exception) {
            // 异常返回给Paging框架,后续会透传到LoadState中
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, GitRepo>): Int? {
        return state.anchorPosition?.let { anchor ->
            state.closestPageToPosition(anchor)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchor)?.nextKey?.minus(1)
        }
    }
}

2.4 修改ViewModel暴露状态

class GitRepoViewModel(private val repository: GitRepoRepository) : ViewModel() {
    // 分页数据直接暴露,自动缓存
    val gitReposList = repository.getListData().cachedIn(viewModelScope)

    // 错误事件,一次性消费
    private val _errorMsg = MutableLiveData<Event<String>>()
    val errorMsg: LiveData<Event<String>> = _errorMsg

    // 加载状态
    private val _loadState = MutableLiveData<Resource<Unit>>()
    val loadState: LiveData<Resource<Unit>> = _loadState

    // 供UI层调用,上报加载错误
    fun onLoadError(throwable: Throwable) {
        val errorMsg = throwable.localizedMessage ?: "未知加载错误"
        _loadState.postValue(Resource.Error(errorMsg, throwable))
        _errorMsg.postValue(Event(errorMsg))
    }

    // 供UI层调用,上报加载状态变化
    fun onLoading() {
        _loadState.postValue(Resource.Loading)
    }

    fun onLoadSuccess() {
        _loadState.postValue(Resource.Success(Unit))
    }
}

2.5 修改Activity实现异常展示

class PagingActivity : AppCompatActivity() {
    private lateinit var gitRepoViewModel: GitRepoViewModel
    private lateinit var recyclerViewAdapter: GitRepoAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 省略初始化逻辑

        // 观察分页数据提交给Adapter
        gitRepoViewModel.gitReposList.observe(this) { pagingData ->
            recyclerViewAdapter.submitData(lifecycle, pagingData)
        }

        // 监听Adapter加载状态(纯回调,无需Flow)
        recyclerViewAdapter.addLoadStateListener { loadStates ->
            // 处理下拉刷新状态
            when (val refreshState = loadStates.refresh) {
                is LoadState.Loading -> gitRepoViewModel.onLoading()
                is LoadState.Error -> gitRepoViewModel.onLoadError(refreshState.error)
                is LoadState.NotLoading -> gitRepoViewModel.onLoadSuccess()
            }

            // 处理上拉加载更多的异常
            val appendState = loadStates.append
            if (appendState is LoadState.Error) {
                gitRepoViewModel.onLoadError(appendState.error)
            }
        }

        // 观察错误事件,展示到TextView
        gitRepoViewModel.errorMsg.observe(this) { event ->
            event.getContentIfNotHandled()?.let { msg ->
                // 直接赋值到你的错误提示TextView
                errorTextView.text = msg
                // 可选:3秒后自动隐藏错误提示
                errorTextView.postDelayed({ errorTextView.visibility = View.GONE }, 3000)
            }
        }

        // 观察加载状态,控制加载弹窗显隐
        gitRepoViewModel.loadState.observe(this) { resource ->
            when(resource) {
                is Resource.Loading -> loadingProgress.visibility = View.VISIBLE
                is Resource.Success, is Resource.Error -> loadingProgress.visibility = View.GONE
            }
        }
    }
}

3 注意事项

  • 不需要把PagingData包装到Resource里,PagingData本身是流式分页数据,和加载/错误状态分开管理更合理
  • 所有状态都经过ViewModel处理,配置变更(比如旋转屏幕)不会丢失状态,也不会重复触发错误提示
  • 如果你不需要在ViewModel层处理异常,也可以直接在Activity的addLoadStateListener回调里直接把错误赋值到TextView,代码更简洁

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 05:15:01