基于协程的Firestore GeoHash查询无结果问题求助
Firestore GeoHash异步查询协程实现问题及解决
问题描述
尝试用Kotlin协程实现Firestore的GeoHash异步查询,但返回的userList始终为空,代码如下:
override suspend fun getUsersWithGeoHash( latitude: Double, longitude: Double, radiusInMeter: Int ): List<UserEntity> { val center = GeoLocation(latitude, longitude) val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInMeter.toDouble()) val tasks: MutableList<Task<QuerySnapshot>> = mutableListOf() var docs = mutableListOf<DocumentSnapshot>() var userList = mutableListOf<UserEntity>() bounds.forEach { geoQueryBounds -> val query = fireStore.collection("database/user/userList") .orderBy("geohash") .startAt(geoQueryBounds.startHash) .endAt(geoQueryBounds.endHash) tasks.add(query.get()) } Log.d("user-get", "before->"+ tasks.size) Log.d("user-get", "before->"+ docs.size) var deferred: Deferred<Task<List<Task<*>>>> = CoroutineScope(Dispatchers.IO).async { Tasks.whenAllComplete(tasks) .addOnCompleteListener { Log.d("user-get", "on-0-> addOnComplete") tasks.forEach { task -> val snapshot = task.result Log.d("user-get", "on->"+ task.toString()) snapshot.documents.forEach { docs.add(it) } } } } Log.d("user-get", "after ->"+ docs.size) val result = deferred.await() Log.d("user-get", "result -> "+ result.toString()) Log.d("user-get", "after ->"+ docs.size) docs.forEach { documentSnapshot -> val user = documentSnapshot.toObject(UserEntity::class.java) user?.let { userList.add(it) } } return userList }
日志输出顺序如下:
before -> 3 before -> 0 after ->0 result -> com.google.android.gms.tasks.zzw@a0897cc after ->0 user-getwithhash: size -> 0 (来自ViewModel) on-0-> addOnComplete on->com.google.android.gms.tasks.zzw@539056a on->com.google.android.gms.tasks.zzw@2e9ca5b on->com.google.android.gms.tasks.zzw@69512f8
问题原因
- 协程与Tasks回调时序不匹配:
deferred.await()仅等待Tasks.whenAllComplete(tasks)返回的Task对象创建完成,而非等待所有查询完成并执行完回调逻辑。回调是异步执行的,协程会直接跳过回调继续执行后续代码,此时docs集合还未被填充。 - 缺少精确距离过滤:GeoHash查询返回的是GeoHash范围内的所有文档,其中部分文档的实际坐标可能超出指定半径,需要额外计算距离进行过滤。
正确实现方案
先确保引入kotlinx-coroutines-play-services依赖,它提供了Task的挂起函数扩展,可实现协程风格的Firestore异步操作。
修正后的代码
import com.google.firebase.firestore.ktx.toObject import com.google.firebase.geofire.GeoFireUtils import com.google.firebase.geofire.GeoLocation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.withContext override suspend fun getUsersWithGeoHash( latitude: Double, longitude: Double, radiusInMeter: Int ): List<UserEntity> = withContext(Dispatchers.IO) { val center = GeoLocation(latitude, longitude) val bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInMeter.toDouble()) // 为每个GeoHash边界创建异步查询任务 val queryDeferreds = bounds.map { geoQueryBounds -> async { fireStore.collection("database/user/userList") .orderBy("geohash") .startAt(geoQueryBounds.startHash) .endAt(geoQueryBounds.endHash) .get() .await() // 转换为挂起函数,等待查询完成 } } // 等待所有查询完成,合并结果 val allSnapshots = queryDeferreds.awaitAll() val allDocuments = allSnapshots.flatMap { it.documents } // 过滤出实际在半径范围内的用户,并转换为UserEntity allDocuments.mapNotNull { doc -> doc.toObject<UserEntity>()?.also { user -> // 假设UserEntity包含latitude和longitude字段 val userLocation = GeoLocation(user.latitude, user.longitude) val distanceInMeters = GeoFireUtils.getDistanceBetween(userLocation, center) if (distanceInMeters > radiusInMeter) null else user } } }
关键改动说明
- 用协程扩展替代回调:通过
await()扩展将Firestore的Task转换为挂起函数,确保协程等待查询完成后再继续执行,避免回调嵌套导致的时序问题。 - 并行查询与结果合并:用
async启动每个GeoHash边界的查询,通过awaitAll等待所有查询完成,统一收集所有结果文档。 - 添加距离过滤:GeoHash查询仅缩小范围,需计算每个用户坐标与中心点的距离,剔除超出指定半径的结果,保证返回数据的准确性。
- 指定IO线程执行:用
withContext(Dispatchers.IO)将整个函数切换到IO线程,符合Firestore操作的线程要求。
内容的提问来源于stack exchange,提问作者tring yuo
相关产品推荐
相关产品推荐

