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

如何在协程中通过循环调用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承担数据逻辑,违反单一职责滚动加载更多、用户触发加载的场景

注意事项

  1. 错误处理:根据需求决定加载失败时是否继续加载后续页,比如在Repository的Flow中可以选择emit错误后继续循环,或者终止流。
  2. 线程管理:Repository层用flowOn(Dispatchers.IO)统一指定IO线程,ViewModel无需额外切换线程,collect默认在Main线程执行。
  3. 状态合并:每次加载新页时要与已有数据合并,避免覆盖之前的UI内容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 21:14:58