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

基于协程的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

问题原因

  1. 协程与Tasks回调时序不匹配:deferred.await()仅等待Tasks.whenAllComplete(tasks)返回的Task对象创建完成,而非等待所有查询完成并执行完回调逻辑。回调是异步执行的,协程会直接跳过回调继续执行后续代码,此时docs集合还未被填充。
  2. 缺少精确距离过滤: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
        }
    }
}

关键改动说明

  1. 用协程扩展替代回调:通过await()扩展将Firestore的Task转换为挂起函数,确保协程等待查询完成后再继续执行,避免回调嵌套导致的时序问题。
  2. 并行查询与结果合并:用async启动每个GeoHash边界的查询,通过awaitAll等待所有查询完成,统一收集所有结果文档。
  3. 添加距离过滤:GeoHash查询仅缩小范围,需计算每个用户坐标与中心点的距离,剔除超出指定半径的结果,保证返回数据的准确性。
  4. 指定IO线程执行:用withContext(Dispatchers.IO)将整个函数切换到IO线程,符合Firestore操作的线程要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 00:25:28