Android:使用Kotlin协程等待回调结果——从RxJava迁移时mapByPage调用挂起函数报错的解决方案咨询
解决在
mapByPage中调用挂起函数的问题 首先,咱们先理清你代码里的两个核心问题:
viewModelScope.launch { ... }.await()是错误用法:launch返回的是Job对象,它没有await方法——只有async返回的Deferred才有这个方法。- 根本矛盾:
mapByPage的lambda参数不是挂起函数,所以你不能直接在里面调用挂起函数(比如fetchProducts)或者await。
下面是几种可行的解决方案,按推荐程度排序:
方案1:补全挂起函数错误处理 + 使用库的协程扩展(最优)
首先先把你fetchProducts里的错误处理补全,不然异常会被吞掉,上层完全感知不到加载失败:
suspend fun fetchProducts(ids: List<String>) = suspendCoroutine<List<ProductItem>> { continuation -> val getProducts = repo.search(ids, null, null) getProducts.subscribe( { callResult -> productRequestInProgress.postValue(false) continuation.resume(callResult.result()) }, { error -> productRequestInProgress.postValue(false) continuation.resumeWithException(error) // 必须补上这一行! } ) }
如果你使用的搜索库(比如Algolia)提供了协程支持,建议直接用库的挂起版API替换手动的suspendCoroutine,代码会更简洁可靠:
suspend fun fetchProducts(ids: List<String>): List<ProductItem> { return try { val callResult = repo.search(ids, null, null).await() // 用库提供的挂起版search productRequestInProgress.postValue(false) callResult.result() } catch (error: Exception) { productRequestInProgress.postValue(false) throw error // 抛出让上层处理异常 } }
接下来检查库是否提供了支持挂起lambda的mapByPage版本(比如命名为mapByPageSuspend),如果有的话直接使用:
val dataSourceFactory = SearcherSingleIndexDataSource.Factory(searcher) { hit -> Product(hit.json.getValue("id").jsonPrimitive.content) }.mapByPageSuspend { page -> // 注意是挂起版的mapByPage fetchProducts(page.mapNotNull { it.id }) }
方案2:临时用runBlocking(谨慎使用)
如果你的mapByPage是在后台线程执行的,可以用runBlocking把挂起操作转换成同步调用,但要严格控制线程:
val dataSourceFactory = SearcherSingleIndexDataSource.Factory(searcher) { hit -> Product(hit.json.getValue("id").jsonPrimitive.content) }.mapByPage { page -> // 指定IO线程,绝对不能在UI线程用runBlocking,会导致ANR runBlocking(Dispatchers.IO) { fetchProducts(page.mapNotNull { it.id }) } }
⚠️ 注意:如果mapByPage在UI线程执行,这个方法会阻塞UI导致应用无响应,所以一定要确保代码块跑在后台线程。
方案3:自定义DataSource(最灵活,适配分页场景)
如果上面的方法都不适用,你可以自定义一个DataSource,在数据加载逻辑里直接用协程调用fetchProducts,完全绕开mapByPage的限制:
class ProductsDataSource( private val searcher: SearcherSingleIndex, private val viewModelScope: CoroutineScope ) : PageKeyedDataSource<Int, ProductItem>() { override fun loadInitial( params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, ProductItem> ) { viewModelScope.launch { try { // 1. 从搜索器获取初始分页数据(根据你的实际搜索逻辑调整) val initialHits = searcher.search(...).result() val productIds = initialHits.mapNotNull { it.id } // 2. 调用挂起函数获取产品详情 val products = fetchProducts(productIds) // 3. 把结果回调给分页框架 callback.onResult(products, null, 1) } catch (e: Exception) { // 处理加载失败,比如回调错误或触发重试 e.printStackTrace() } finally { productRequestInProgress.postValue(false) } } } override fun loadAfter( params: LoadParams<Int>, callback: LoadCallback<Int, ProductItem> ) { viewModelScope.launch { try { // 加载下一页的搜索结果 val nextHits = searcher.search(...).result() val productIds = nextHits.mapNotNull { it.id } val products = fetchProducts(productIds) callback.onResult(products, params.key + 1) } catch (e: Exception) { e.printStackTrace() } finally { productRequestInProgress.postValue(false) } } } override fun loadBefore( params: LoadParams<Int>, callback: LoadCallback<Int, ProductItem> ) { // 不需要加载上一页的话,空实现即可 } }
然后创建对应的DataSourceFactory:
val dataSourceFactory = object : DataSource.Factory<Int, ProductItem>() { override fun create(): DataSource<Int, ProductItem> { return ProductsDataSource(searcher, viewModelScope) } }
内容的提问来源于stack exchange,提问作者GraSim
相关产品推荐
相关产品推荐

