Home > Software design >  Return value from suspend function when other flow is completed
Return value from suspend function when other flow is completed

Time:10-07

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()
}

CodePudding user response:

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(EuvicMobileSDK.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.

CodePudding user response:

.addOnSuccessListener { trySend(it) }

  • Related