androidkotlinkotlin-coroutinescoroutinescope

Return value from suspend function when other flow is completed


I have two suspend functions which are callbackFlow. I call one of them from suspend function which return String. I want to wait for location from getLocation() in serializeEvent() function and after getting value return string.

suspend fun getLocation(applicationContext: Context) = callbackFlow {
            val locationProvider = LocationServices.getFusedLocationProviderClient(applicationContext)

            if (isLocationPermissionGranted(applicationContext)) {
                locationProvider.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
                    .addOnSuccessListener {
                        if (it != null) {
                            trySend(it)
                        } else {
                            launch { trySend(getLastKnownLocation(locationProvider)) }
                        }
                    }
    }
}

private suspend fun getLastKnownLocation(locationProvider: FusedLocationProviderClient) = callbackFlow {
        runCatching {
            locationProvider.lastLocation
                .addOnSuccessListener { trySend(it) }
                .addOnCanceledListener { trySend(null) }
                .addOnFailureListener { trySend(null) }
        }.onFailure {
          trySend(null)
        }
        awaitClose { this.cancel() }
    }

How I can return string when I get value from getLastKnownLocation()

suspend fun serializeEvent(eventJson: JSONObject): String  {

  CoroutineScope(Dispatchers.Default).launch {
            LocationProvider.getLocation(getApplicationContext!!).collect {
            }
  }

   //some code here
   return eventJson.toString()
}

Solution

  • It doesn't make sense to turn a Google Task into a Flow using callbackFlow, because a Task only produces one thing, not a series of things. Typically, the way to convert for coroutines a callback that returns only once is to convert it into a suspend function using suspendCoroutine or suspendCancellableCoroutine.

    However, a Task.await() extension suspend function is already provided so you can use it synchronously with coroutines. (Make sure you're using the -ktx version of the location library.) Instead of using listeners, you surround it with try/catch.

    It seems like you just want to return null if no location is available, so here's how I would rewrite that function:

    suspend fun getLocationOrNull(applicationContext: Context): Location? {
        if (!isLocationPermissionGranted(applicationContext)) {
            return null
        }
    
        val locationProvider = LocationServices.getFusedLocationProviderClient(applicationContext)
        return try {
            locationProvider.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null)
                .await() ?: locationProvider.lastLocation.await()
        } catch (e: Exception) {
            Log.w(TAG, "Failed to retrieve current location")
            null
        }
    }
    

    In your usage site code, it doesn't make sense that you're launching a coroutine from within the suspend function. If you want to do something and wait for it in a suspend function, you can just do it directly without firing off a new coroutine in some other CoroutineScope.

    suspend fun serializeEvent(eventJson: JSONObject): String  {
        val knownLocation: Location? = 
            LocationProvider.getLocation(getApplicationContext!!)
    
       //some code here. Do something with knownLocation, which might be null
       return eventJson.toString()
    }
    

    Side note, it looks like a code smell to me that you have to use !! when getting your application context. An Application Context must always exist, so it shouldn't need to be a nullable property.