如何在协程中通过循环调用API实现Flickr应用分页加载?
分页实现方案建议与代码示例
方案选择原则
分页逻辑的分层设计要遵循关注点分离:Repository负责封装数据获取逻辑,ViewModel负责UI状态管理和用户交互响应。针对你的需求(自动逐页加载并实时更新UI),两种分层实现都可行,各有优劣,具体选择取决于后续需求的灵活性。
方案一:Repository层封装全量分页加载(推荐用于自动全量加载场景)
将逐页调用API的逻辑封装在Repository层,返回一个Flow来逐页发射数据,ViewModel只需收集这个Flow并更新UI状态,职责更清晰。
Repository层修改
override fun getRecentAllPhotos(): Flow<FlickrResult<FlickrPhotosList?>> = flow { var currentPage = 1 var totalPages = 1 do { val apiResponse = flickrApiHelper.getRecentPhotos(currentPage) val result = when (apiResponse) { is FlickrApiResult.OnSuccess -> { val photosList = FlickrMapper.fromRetrofitToFlickrPhotosList(apiResponse.data) totalPages = photosList?.pages ?: 1 FlickrResult.OnSuccess(photosList) } is FlickrApiResult.OnError -> FlickrResult.OnError(apiResponse.exception) } // 发射当前页数据,ViewModel会收到并更新UI emit(result) currentPage++ } while (currentPage <= totalPages) }.flowOn(Dispatchers.IO) // 统一指定IO线程,避免重复切换
ViewModel层修改
fun loadAllRecentPhotos() { viewModelScope.launch { _flickerPhotoState.value = FlickrState.Loading flickrUseCases.getRecentAllPhotos().collect { result -> when (result) { is FlickrResult.OnSuccess -> { result.data?.photo?.let { newPhotos -> if (newPhotos.isNotEmpty()) { // 获取当前已展示的照片列表,追加新页数据 val currentPhotos = (_flickerPhotoState.value as? FlickrState.DisplayPhotos)?.photos ?: emptyList() _flickerPhotoState.value = FlickrState.DisplayPhotos( currentPhotos + Mapper.fromListFlickrPhotoItemToListPhotoDetails(newPhotos) ) } else { // 如果当前没有任何数据,显示无照片状态 if ((_flickerPhotoState.value as? FlickrState.DisplayPhotos)?.photos.isNullOrEmpty()) { _flickerPhotoState.value = FlickrState.NoPhotos } } } ?: run { if ((_flickerPhotoState.value as? FlickrState.DisplayPhotos)?.photos.isNullOrEmpty()) { _flickerPhotoState.value = FlickrState.NoPhotos } } } is FlickrResult.OnError -> { _flickerPhotoState.value = FlickrState.Error(result.exception) // 可选:遇到错误是否停止加载后续页?这里取消协程终止加载 cancel() } } } } }
方案二:ViewModel层处理分页逻辑(适合按需加载场景)
如果后续需要支持滚动加载更多(而非一次性全量加载),可以让ViewModel维护当前页码,在需要时触发单页请求,这种方式更灵活。
Repository层保持单页请求逻辑
无需修改原有getRecentPhotos(page: Int)方法,保持只获取单页数据的职责。
ViewModel层修改
private var currentPage = 1 private var totalPages = 1 fun loadNextPage() { if (currentPage > totalPages) return viewModelScope.launch { // 可选:显示加载更多状态 _flickerPhotoState.value = FlickrState.LoadingMore when(val result = flickrUseCases.getRecentPhotos(currentPage)) { is FlickrResult.OnSuccess -> { result.data?.let { photosList -> totalPages = photosList.pages ?: 1 photosList.photo?.let { newPhotos -> if (newPhotos.isNotEmpty()) { val currentPhotos = (_flickerPhotoState.value as? FlickrState.DisplayPhotos)?.photos ?: emptyList() _flickerPhotoState.value = FlickrState.DisplayPhotos( currentPhotos + Mapper.fromListFlickrPhotoItemToListPhotoDetails(newPhotos) ) currentPage++ } else { if ((_flickerPhotoState.value as? FlickrState.DisplayPhotos)?.photos.isNullOrEmpty()) { _flickerPhotoState.value = FlickrState.NoPhotos } } } ?: run { if ((_flickerPhotoState.value as? FlickrState.DisplayPhotos)?.photos.isNullOrEmpty()) { _flickerPhotoState.value = FlickrState.NoPhotos } } } } is FlickrResult.OnError -> { _flickerPhotoState.value = FlickrState.Error(result.exception) } } } } // 初始化加载第一页 fun initLoadRecentPhotos() { currentPage = 1 totalPages = 1 viewModelScope.launch { _flickerPhotoState.value = FlickrState.Loading loadNextPage() } }
两种方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Repository层封装 | 数据逻辑集中复用,ViewModel职责单一 | 灵活性低,难以中途控制加载流程 | 一次性自动加载所有页的场景 |
| ViewModel层处理 | 灵活支持按需加载,精细控制UI状态 | ViewModel承担数据逻辑,违反单一职责 | 滚动加载更多、用户触发加载的场景 |
注意事项
- 错误处理:根据需求决定加载失败时是否继续加载后续页,比如在Repository的Flow中可以选择emit错误后继续循环,或者终止流。
- 线程管理:Repository层用
flowOn(Dispatchers.IO)统一指定IO线程,ViewModel无需额外切换线程,collect默认在Main线程执行。 - 状态合并:每次加载新页时要与已有数据合并,避免覆盖之前的UI内容。
内容的提问来源于stack exchange,提问作者Seb
相关产品推荐
相关产品推荐

