Clean Architecture+Paging3+Compose:规避PagingData入领域层的类型错误
Clean Architecture 结合 Paging3 实现 LazyColumn 数据填充的类型匹配方案
核心问题梳理
你当前的矛盾点在于:
- 领域层需要保持对 Android SDK 的独立,不能直接依赖
PagingData - 数据层用 Paging3 实现时,得到的是
Flow<PagingData<ResponseState.Success<List<ComplexSearchEntity>>>>,与领域层要求的Flow<ResponseState<List<ComplexSearchEntity>>>类型不匹配 - 最终要在 UI 层将数据转为
LazyPagingItems<ComplexSearchEntity>填充 LazyColumn
解决方案步骤
1. 领域层定义独立的分页抽象(隔离 Android 依赖)
通过依赖倒置原则,在领域层定义自己的分页接口和状态模型,完全不依赖 Android SDK:
// 领域层通用加载状态 sealed class LoadState { object Loading : LoadState() data class Error(val message: String) : LoadState() object Idle : LoadState() } // 领域层分页数据抽象接口 interface DomainPagingData<T> { fun getDataFlow(): Flow<List<T>> fun getLoadStateFlow(): Flow<LoadState> } // 领域层 Repository 接口 interface ComplexSearchRepository { fun getPagedComplexSearch(): DomainPagingData<ComplexSearchEntity> }
2. 数据层实现分页逻辑并转换结果
在数据层处理 ResponseState 和 Paging3 的细节,将 Android 原生的 PagingData 封装为领域层的 DomainPagingData:
第一步:实现处理 ResponseState 的 PagingSource
class ComplexSearchPagingSource( private val apiService: ApiService ) : PagingSource<Int, ComplexSearchEntity>() { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ComplexSearchEntity> { val page = params.key ?: 1 return try { // 调用接口获取原始响应 val response = apiService.getComplexSearch(page = page) when (response) { is ResponseState.Success -> { val dataList = response.data val nextKey = if (dataList.size < params.loadSize) null else page + 1 val prevKey = if (page == 1) null else page - 1 // 直接返回实体列表的分页结果 LoadResult.Page( data = dataList, prevKey = prevKey, nextKey = nextKey ) } is ResponseState.Error -> LoadResult.Error(Throwable(response.message)) ResponseState.Loading -> LoadResult.Error(Throwable("加载中")) } } catch (e: Exception) { LoadResult.Error(e) } } override fun getRefreshKey(state: PagingState<Int, ComplexSearchEntity>): Int? { return state.anchorPosition?.let { anchorPosition -> state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) } } }
第二步:实现领域层的 Repository 接口
class ComplexSearchRepositoryImpl( private val apiService: ApiService ) : ComplexSearchRepository { private val pagingConfig = PagingConfig(pageSize = 20) private val pager = Pager(config = pagingConfig) { ComplexSearchPagingSource(apiService) } override fun getPagedComplexSearch(): DomainPagingData<ComplexSearchEntity> { return object : DomainPagingData<ComplexSearchEntity> { override fun getDataFlow(): Flow<List<ComplexSearchEntity>> { // 将 PagingData 转换为实体列表流 return pager.flow.map { it.toList() } } override fun getLoadStateFlow(): Flow<LoadState> { // 将 Paging3 的 CombinedLoadStates 转换为领域层 LoadState return pager.loadStateFlow.map { combinedStates -> when { combinedStates.refresh is LoadState.Loading -> LoadState.Loading combinedStates.refresh is LoadState.Error -> { val error = combinedStates.refresh as LoadState.Error LoadState.Error(error.error.message ?: "未知错误") } else -> LoadState.Idle } } } } } }
3. UI 层转换为 LazyPagingItems
在 ViewModel 中获取封装后的分页数据,转换为 Android 原生的 LazyPagingItems 供 LazyColumn 使用:
class ComplexSearchViewModel( private val repository: ComplexSearchRepository ) : ViewModel() { // 获取数据层的原生 PagingData 流(通过类型转换,仅在 UI 层依赖 Android SDK) private val pagingDataFlow: Flow<PagingData<ComplexSearchEntity>> = (repository.getPagedComplexSearch() as? DomainPagingDataWrapper)?.pager?.flow ?: emptyFlow() // 转换为 LazyPagingItems 并缓存 val lazyPagingItems: LazyPagingItems<ComplexSearchEntity> = pagingDataFlow.cachedIn(viewModelScope).collectAsLazyPagingItems() // 加载状态流 val loadStateFlow: Flow<LoadState> = repository.getPagedComplexSearch().getLoadStateFlow() } // Compose UI 层使用 @Composable fun ComplexSearchScreen(viewModel: ComplexSearchViewModel) { LazyColumn { items(viewModel.lazyPagingItems) { entity -> entity?.let { SearchItem(it) } } // 处理加载状态 viewModel.lazyPagingItems.apply { when { loadState.refresh is androidx.paging.LoadState.Loading -> { item { LoadingIndicator() } } loadState.refresh is androidx.paging.LoadState.Error -> { val error = loadState.refresh as androidx.paging.LoadState.Error item { ErrorRetryItem(error.error.message ?: "加载失败") { retry() } } } } } } }
关键优化点
- 将 ResponseState 处理移到 PagingSource 内部:避免 PagingData 包裹 ResponseState,直接返回实体类型的分页数据,从根源解决类型不匹配问题
- 依赖倒置隔离 Android 依赖:领域层只依赖自己定义的抽象接口,完全独立于 Android SDK,符合 Clean Architecture 分层原则
- UI 层仅做适配转换:将领域层的抽象分页数据转换为 Android 原生的 LazyPagingItems,不影响核心业务逻辑的独立性
内容的提问来源于stack exchange,提问作者ant2009
相关产品推荐
相关产品推荐

