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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 10:06:22