如何让placeRequest获取findAutocompletePredictions的首个预测结果?
解决Google Places API异步操作的结果传递问题
这个问题我太熟了!Google Places API的异步操作确实容易踩这个坑——因为findAutocompletePredictions是异步执行的,你写在外面的fetchPlaceRequest代码会先运行,这时候不仅拿不到预测结果,用全局变量也会因为异步时序问题变成null。给你几个靠谱的解决办法:
方案一:直接嵌套异步回调(最直观的改法)
把获取Place的逻辑直接放到自动补全的成功回调里,这样确保拿到第一个预测结果后再执行fetchPlace操作:
class PlacesApi(context: Context, apiKey: String) { private val placesClient: PlacesClient private val token: AutocompleteSessionToken private lateinit var place: Place init { Places.initialize(context, apiKey) placesClient = Places.createClient(context) token = AutocompleteSessionToken.newInstance() } fun getCityPlaceId(inputString: String) { val userInputRequest = FindAutocompletePredictionsRequest.builder() .setTypeFilter(TypeFilter.CITIES) .setSessionToken(token) .setQuery(inputString) .build() placesClient.findAutocompletePredictions(userInputRequest) .addOnSuccessListener { response: FindAutocompletePredictionsResponse -> // 取第一个预测结果,为空时做容错处理 val firstPrediction = response.autocompletePredictions.firstOrNull() firstPrediction?.let { prediction -> Log.d("Place ID", prediction.placeId) Log.d("Place City", prediction.getPrimaryText(null).toString()) Log.d("Place Country", prediction.getSecondaryText(null).toString()) // 拿到placeId后再创建请求并获取Place详情 val placeFields: List<Place.Field> = listOf(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS) val placeRequest = FetchPlaceRequest.newInstance(prediction.placeId, placeFields) placesClient.fetchPlace(placeRequest) .addOnSuccessListener { fetchPlaceResponse: FetchPlaceResponse -> place = fetchPlaceResponse.place Log.i("Place coordinates: ", place.latLng?.longitude.toString()) Log.i("Place coordinates: ", place.latLng?.latitude.toString()) Log.i("Place name: ", place.name.toString()) Log.i("Place address: ", place.address.toString()) }.addOnFailureListener { if (it is ApiException) { Log.e("Place not found", "Place not found " + it.message) } } } ?: run { Log.w("Prediction", "No autocomplete predictions found") } } .addOnFailureListener { Log.e("Autocomplete error", "Failed to get predictions: ${it.message}") } } }
方案二:使用回调函数解耦代码
如果不想嵌套太多回调,可以给getCityPlaceId添加一个回调参数,拿到最终Place结果后通知调用者:
// 先定义回调接口 interface PlaceResultCallback { fun onPlaceLoaded(place: Place) fun onError(message: String) } class PlacesApi(context: Context, apiKey: String) { private val placesClient: PlacesClient private val token: AutocompleteSessionToken init { Places.initialize(context, apiKey) placesClient = Places.createClient(context) token = AutocompleteSessionToken.newInstance() } fun getCityPlaceId(inputString: String, callback: PlaceResultCallback) { val userInputRequest = FindAutocompletePredictionsRequest.builder() .setTypeFilter(TypeFilter.CITIES) .setSessionToken(token) .setQuery(inputString) .build() placesClient.findAutocompletePredictions(userInputRequest) .addOnSuccessListener { response: FindAutocompletePredictionsResponse -> val firstPrediction = response.autocompletePredictions.firstOrNull() firstPrediction?.let { prediction -> val placeFields: List<Place.Field> = listOf(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS) val placeRequest = FetchPlaceRequest.newInstance(prediction.placeId, placeFields) placesClient.fetchPlace(placeRequest) .addOnSuccessListener { fetchPlaceResponse -> callback.onPlaceLoaded(fetchPlaceResponse.place) }.addOnFailureListener { val errorMsg = if (it is ApiException) "Place not found: ${it.message}" else "Failed to fetch place: ${it.message}" callback.onError(errorMsg) } } ?: callback.onError("No autocomplete predictions found") } .addOnFailureListener { callback.onError("Failed to get autocomplete predictions: ${it.message}") } } } // 调用示例 // placesApi.getCityPlaceId("Beijing", object : PlaceResultCallback { // override fun onPlaceLoaded(place: Place) { // // 处理拿到的Place对象 // } // // override fun onError(message: String) { // // 处理错误 // } // })
方案三:用Kotlin协程简化异步代码(推荐)
Kotlin的协程可以把异步操作转换成类似同步的写法,避免回调地狱,代码更简洁易读:
首先确保你的项目已经引入协程依赖,然后修改代码:
import com.google.android.gms.tasks.await class PlacesApi(context: Context, apiKey: String) { private val placesClient: PlacesClient private val token: AutocompleteSessionToken init { Places.initialize(context, apiKey) placesClient = Places.createClient(context) token = AutocompleteSessionToken.newInstance() } // 把方法改成挂起函数,需要在协程作用域内调用 suspend fun getCityPlaceId(inputString: String): Place? { return try { // 等待自动补全结果返回 val userInputRequest = FindAutocompletePredictionsRequest.builder() .setTypeFilter(TypeFilter.CITIES) .setSessionToken(token) .setQuery(inputString) .build() val autocompleteResponse = placesClient.findAutocompletePredictions(userInputRequest).await() val firstPrediction = autocompleteResponse.autocompletePredictions.firstOrNull() firstPrediction?.let { prediction -> // 等待Place详情结果返回 val placeFields: List<Place.Field> = listOf(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS) val placeRequest = FetchPlaceRequest.newInstance(prediction.placeId, placeFields) val fetchPlaceResponse = placesClient.fetchPlace(placeRequest).await() fetchPlaceResponse.place } ?: run { Log.w("Prediction", "No autocomplete predictions found") null } } catch (e: Exception) { val errorMsg = if (e is ApiException) "Place error: ${e.message}" else "Async error: ${e.message}" Log.e("PlacesApi", errorMsg) null } } } // 调用示例(需要在协程作用域内,比如Activity的lifecycleScope) // lifecycleScope.launch { // val place = placesApi.getCityPlaceId("Shanghai") // place?.let { // // 处理Place对象 // } // }
这三种方案里,协程的写法最优雅,也最容易维护,推荐优先使用。
内容的提问来源于stack exchange,提问作者Jax
相关产品推荐
相关产品推荐

