在项目中,当需要对在线数据和本地数据进行混合分页处理时,Paging3 提供了强大的工具,能够帮助我们优雅地实现数据加载和切换逻辑。然而,对于一些复杂的场景,例如在线数据与本地数据的动态切换,或者在没有在线数据时优雅降级到本地数据,这些仍然需要我们做一些额外的处理和优化。
一、背景与需求
我们的项目需要实现一个复杂的分页逻辑:
- 在线与本地数据的混合排序:当有在线数据时,在线数据与本地数据按时间排序混合展示。
- 无在线数据时降级为本地数据:如果在线数据为空或加载失败,展示本地存储的数据。
- 刷新和切换逻辑:支持在不同状态下自由切换在线与本地数据源。
二、基础实现步骤
1. 实现 PagingSource
PagingSource 是 Paging3 的核心组件,用于处理分页加载逻辑。我们需要实现一个通用的数据源,在加载在线数据失败时切换到本地数据源。
2. 在线与本地数据切换逻辑
在 PagingSource 中,数据请求失败时,查询本地数据并返回。
3. 数据源切换
当用户刷新或需要重新加载数据时,动态切换在线和本地数据源。
以下是具体代码实现示例:
class ListPagingSource(private val token: String) : PagingSource<Int, Record>() {
private val mRecordDao by lazy { AppDataBase.getInstance().getRecordDao() }
override fun getRefreshKey(state: PagingState<Int, Record>): Int? {
// 定义刷新关键点
return null
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Record> {
return try {
if (token.isEmpty()) {
throw IllegalArgumentException("Token 不能为空")
}
val page = params.key ?: FIRST_PAGE_INDEX
val response = Service.getPage(token, page) // 请求在线数据
val responseData = response.data
if (response.isSuccess && responseData != null) {
// 返回在线数据
LoadResult.Page(
data = responseData.records,
prevKey = if (page > FIRST_PAGE_INDEX) page - 1 else null,
nextKey = if (page < responseData.totalPage) page + 1 else null
)
} else {
// 切换到本地数据
val localData = mRecordDao.getAllRecord()
LoadResult.Page(data = localData, prevKey = null, nextKey = null)
}
} catch (e: Exception) {
// 错误处理
LoadResult.Error(e)
}
}
companion object {
private const val FIRST_PAGE_INDEX = 1
}
}
三、本地数据不分页的切换方案
如果本地数据量较小或者业务场景不复杂,可以不对本地数据进行分页处理,而是直接在加载在线数据失败时展示全部本地数据。此方案实现简单,适合轻量级场景,但当数据量较大时,用户体验可能会受到影响。
实现示例:
if (response.isSuccess) {
// 展示在线数据
LoadResult.Page(data = responseData.records, prevKey = prevKey, nextKey = nextKey)
} else {
// 加载全部本地数据
val localData = mRecordDao.getAllRecord()
LoadResult.Page(data = localData, prevKey = null, nextKey = null)
}
优点:实现简单,适合数据量较小的场景。
缺点:数据量大时,加载速度较慢且可能引发性能问题。
四、本地数据分页的切换方案
为了提升大数据量场景下的用户体验,我们可以对本地数据和在线数据分别进行分页处理。以下是方案步骤:
- 为本地数据实现单独的
PagingSource。 - 在线数据和本地数据独立运行,并支持动态切换。
- 解决切换数据源时的冲突问题。
实现独立的本地数据 PagingSource
fun getLocalPagedData(): Flow<PagingData<Record>> {
return Pager(
config = PagingConfig(
pageSize = 30,
enablePlaceholders = false
),
pagingSourceFactory = { dao.getPagedRecords() }
).flow.cachedIn(viewModelScope)
}
切换数据源时避免崩溃
Paging3 在加载数据时直接切换数据源会导致 IndexOutOfBoundsException,解决方法是在切换数据源前先提交一个空的 PagingData。
cloudPageAdapter.submitData(PagingData.empty()) // 提交空数据
cloudPageAdapter.submitData(newPagingData) // 提交新数据源
进一步优化:处理用户点击事件崩溃
如果用户在空数据加载时触发点击事件,也可能导致崩溃。通过延迟加载新数据,可以有效避免这一问题:
launch(Dispatchers.IO) {
cloudPageAdapter.submitData(PagingData.empty())
delay(100) // 延迟 100 毫秒
cloudPageAdapter.submitData(newPagingData)
}
五、总结
Paging3 提供了强大的分页加载能力,但在复杂的场景中,例如在线与本地数据切换时,仍需要我们精心设计和优化。以下是关键点:
- 动态切换数据源时,确保 Paging3 的数据状态一致。
- 本地数据量较小时,可以不分页;但大数据量场景下分页更高效。
- 避免用户在数据切换时操作导致的崩溃问题。