Search code examples
androidkotlinkotlin-coroutinescoroutinescope

Continue suspend functions from callback


I am using firestore as my backend database and saving my data looks like this:

suspend fun uploadDataToFirestore() {
  val firestore = Firebase.firestore
  var batch = firestore.batch
 
 -- fill batch with data --

  batch.commit().addOnCompleteListener {
    if (it.isSuccessful) {
      Timber.d("Successfully saved data")
     
      doAdditionalSuspendingStuff()
            
    } else {
      Timber.d("error at saving data: ${it.exception}")     
    }
}
 

The problem lies inside the onCompleteListener because I am not able to call additional suspending functions. Is there a way to call suspending functions from within the onCompleteListener but so that they are still attached to the same scope because I don't want the function uploadDataToFirestore to finish until doAdditionalSuspendingStuff() is executed.


Solution

  • You can use kotlinx-coroutines-play-services artifact which contains a small set of utilities for converting between coroutines and Tasks API (which you'll also find common in other Play-related libraries).

    You should be able to replace callback-based API (addOnCompleteListener()) with suspending Task.await() extension function:

    suspend fun uploadDataToFirestore() {
        val firestore = Firebase.firestore
        val batch = firestore.batch
    
        try {
            batch.commit().await() // Suspend the coroutine while uploading data.
    
            Timber.d("Successfully saved data")
            doAdditionalSuspendingStuff()
        } catch (exception: Exception) {
            Timber.d("error at saving data: $exception")     
        }
    }
    

    await() also returns an unwrapped result (the T in Task<T>).

    Under the hood it converts Task<T>.addCompleteListener() into a suspending function using suspendCancellableCoroutine. Source code is available here.