首页 > 解决方案 > 如何在 Android 中链接 API 以使用存储库类中的改造、流、NetworkBoundResource 从服务器检索所有页面?

问题描述

我正在开发一个 Android 应用程序,它需要从网络中检索所有页面以在屏幕上显示详细信息。这是 Youtube API https://developers.google.com/youtube/v3/docs/playlistItems/list

每个响应都有 nextPageToken,需要用于下一页 api。应用程序未显示列表,因此不需要分页。我正在使用改造+流+NetworkBoundResource+mvvm+kotlin。如何在存储库中使用任何转换方法 FlatMapMerge/FlatMapConcat/FlatMapLatest 将所有响应放在一起然后发出。

restApiService
@GET(Contracts.PLAYLIST_ITEM_ENDPOINT)
fun getAllPlayListItemsForPlayListId(
    @Query("part") part: String = "snippet,status",
    @Query("playlistId") playlistId: String,
    @Query("pageToken") pageToken: String = "",
    @Query("maxResults") maxResults: Int? = 50
): Flow<ApiResponse<PlaylistItemsResponse>>

网络绑定资源

suspend fun asFlow(): Flow<Resource<ResultType>> {
    return loadFromDb().transformLatest { dbValue ->
        if (shouldFetch(dbValue)) {
            emit(Resource.loading(dbValue))

            createCall().collect { apiResponse ->
                when (apiResponse) {
                    is ApiSuccessResponse -> {
                        withContext(Dispatchers.IO) {
                            saveCallResult(processResponse(apiResponse))
                        }
                    }

                    is ApiEmptyResponse -> {
                        emit(Resource.success(dbValue))
                    }

                    is ApiErrorResponse -> {
                        onFetchFailed()
                        emit(Resource.error(apiResponse.errorMessage, dbValue))
                    }
                }
            }
        } else {
            emit(Resource.success(dbValue))
        }
    }
}

存储库

override suspend fun createCall() =
            apiService.getAllPlayListItemsForPlayListId(playlistId = playlistId,pageToken = nextPageToken)

如何递归调用相同的 api 直到 nextPagenToken 为空,就像这样,它在 Go 中实现,来自 Youtube API 指南。 https://developers.google.com/youtube/v3/docs/playlistItems/list

package main

import (
    "fmt"
    "log"

    "google.golang.org/api/youtube/v3"
)

// Retrieve playlistItems in the specified playlist
func playlistItemsList(service *youtube.Service, part string, playlistId string, pageToken 
string) *youtube.PlaylistItemListResponse {
    call := service.PlaylistItems.List(part)
    call = call.PlaylistId(playlistId)
    if pageToken != "" {
            call = call.PageToken(pageToken)
    }
    response, err := call.Do()
    handleError(err, "")
    return response
}

// Retrieve resource for the authenticated user's channel
func channelsListMine(service *youtube.Service, part string) *youtube.ChannelListResponse {
    call := service.Channels.List(part)
    call = call.Mine(true)
    response, err := call.Do()
    handleError(err, "")
    return response
}

func main() {
    client := getClient(youtube.YoutubeReadonlyScope)
    service, err := youtube.New(client)
    
    if err != nil {
            log.Fatalf("Error creating YouTube client: %v", err)
    }

    response := channelsListMine(service, "contentDetails")

    for _, channel := range response.Items {
            playlistId := channel.ContentDetails.RelatedPlaylists.Uploads
            
            // Print the playlist ID for the list of uploaded videos.
            fmt.Printf("Videos in list %s\r\n", playlistId)

            nextPageToken := ""
            for {
                    // Retrieve next set of items in the playlist.
                    playlistResponse := playlistItemsList(service, "snippet", playlistId, nextPageToken)
                    
                    for _, playlistItem := range playlistResponse.Items {
                            title := playlistItem.Snippet.Title
                            videoId := playlistItem.Snippet.ResourceId.VideoId
                            fmt.Printf("%v, (%v)\r\n", title, videoId)
                    }

                    // Set the token to retrieve the next page of results
                    // or exit the loop if all results have been retrieved.
                    nextPageToken = playlistResponse.NextPageToken
                    if nextPageToken == "" {
                            break
                    }
                    fmt.Println()
            }
    }
} 

标签: androidkotlinmvvmretrofit2kotlin-flow

解决方案


我通过 flatMapLatest 得到了它。

fun getPageAndNext(playlistId: String, nextPageToken: String): Flow<ApiResponse<PlaylistItemsResponse>> {
            return apiService.getAllPlayListItemsForPlayListId(playlistId = playlistId, pageToken = nextPageToken)
                    .flatMapLatest { apiResponse ->
                        var nextPage: String? = null

                        if (apiResponse is ApiSuccessResponse) {
                            nextPage = apiResponse.body.nextPageToken
                        }

                        if (nextPage == null) {
                            flowOf(apiResponse)
                        } else {
                            getPageAndNext(playlistId, nextPage)
                        }
                    }
        }

        override suspend fun createCall() = getPageAndNext(playlistId = playlistId, nextPageToken = nextPageToken)

推荐阅读