如何在Kotlin JVM应用中结合runBlocking使用Apollo Client?
Kotlin JVM 中用 Apollo Client 实现分页数据合并的正确方式
先看你代码里的几个核心问题,这是导致数据为空的直接原因:
- 变量名错误:
FooSource的load方法里,用response接收查询结果,但返回LoadResult时却引用了未定义的result.data,应该替换为response.data(还要注意处理response.data可能为null的情况)。 - 拼写错误:
getData里实例化的是FoodSource,但你的类名是FooSource,这会导致编译错误。 - 参数未初始化:
getData里的params没有初始化,需要先创建PageParams实例,设置初始的pageSize和空游标。 - 分页标记提取错误:你需要从Apollo查询结果的
pageInfo中正确提取endCursor和hasNextPage,而不是凭空生成。
修正后的基础实现
先修复错误,让代码能正常运行:
// 定义分页参数类 data class PageParams( val pageSize: Int, var cursorNext: String? = null ) // 定义加载结果类 sealed class LoadResult<out T> { data class Success<out T>(val data: List<T>, val endCursor: String?, val hasNext: Boolean) : LoadResult<T>() data class Error(val exception: Exception) : LoadResult<Nothing>() } class FooSource(private val query: String, private val apolloClient: ApolloClient) { suspend fun load(params: PageParams): LoadResult<Foo> { return try { val response = apolloClient.query( FooQuery( queryString = query, pageSize = params.pageSize, cursor = params.cursorNext ) ).execute() // 处理GraphQL层面的错误 if (response.hasErrors()) { LoadResult.Error(Exception(response.errors?.joinToString { it.message })) } else { val fooData = response.data?.foos ?: emptyList() val pageInfo = response.data?.pageInfo val endCursor = pageInfo?.endCursor val hasNext = pageInfo?.hasNextPage ?: false println("Source data: ${fooData.size}") LoadResult.Success(fooData, endCursor, hasNext) } } catch (e: Exception) { LoadResult.Error(e) } } } // 合并分页数据的函数 fun getData(query: String, apolloClient: ApolloClient, pageSize: Int = 20): List<Foo> { val source = FooSource(query, apolloClient) val data = mutableListOf<Foo>() val params = PageParams(pageSize = pageSize) var hasNext: Boolean do { val result = runBlocking { source.load(params) } when (result) { is LoadResult.Success -> { println("Received data: ${result.data.size}") data.addAll(result.data) params.cursorNext = result.endCursor hasNext = result.hasNext } is LoadResult.Error -> { throw result.exception // 也可根据需求返回已加载的部分数据 } } } while (hasNext) return data }
更优实现:用 Kotlin Flow 封装分页逻辑
如果想要更优雅的响应式处理,推荐用Flow封装分页流程,避免手动循环:
class FooSource(private val query: String, private val apolloClient: ApolloClient) { fun fetchAllPages(pageSize: Int = 20): Flow<Foo> = flow { var cursor: String? = null do { val response = apolloClient.query( FooQuery( queryString = query, pageSize = pageSize, cursor = cursor ) ).execute() if (response.hasErrors()) { throw Exception(response.errors?.joinToString { it.message }) } val fooData = response.data?.foos ?: emptyList() fooData.forEach { emit(it) } // 逐个发射数据,也可用emitAll(fooData)批量发射 cursor = response.data?.pageInfo?.endCursor } while (response.data?.pageInfo?.hasNextPage == true) }.flowOn(Dispatchers.IO) // 将网络请求切换到IO线程,避免阻塞主线程 } // 使用示例 fun main() = runBlocking { val apolloClient = ApolloClient.Builder() .serverUrl("your-graphql-endpoint") .build() val source = FooSource("your-query-content", apolloClient) val allData = source.fetchAllPages().toList() println("Total fetched data: ${allData.size}") }
关键注意事项
- Apollo版本适配:Apollo 3.x及以上版本中,
apolloClient.query(...).execute()是suspend函数,必须在协程或suspend函数中调用,你的runBlocking用法是合法的,但要确保协程上下文正确。 - 错误处理:除了捕获网络异常,必须处理Apollo返回的GraphQL业务错误(通过
response.hasErrors()判断)。 - 线程调度:命令行应用中
runBlocking会阻塞当前线程,用Flow时通过flowOn(Dispatchers.IO)可将网络请求放到IO线程,避免阻塞主线程。
内容的提问来源于stack exchange,提问作者Rob
相关产品推荐
相关产品推荐

