1

I have a suspendable (updateData) function which takes another suspend function as an argument (transform). In my updateData function I'm making a call to an asynchronous API and I need to pass the result to the transform suspend function. My current problem is that calling the transform function shows the message "suspension functions can be called only within coroutine context".

Here's what the code looks like:

override suspend fun updateData(transform: suspend (prefs: Preferences) -> Preferences): Preferences {
    return suspendCancellableCoroutine { continuation ->
        realtimeDatabase.runTransaction(object : Transaction.Handler {
            override fun doTransaction(currentData: MutableData): Transaction.Result {
                val prefs: Preferences = currentData.toPreferences()

                // I need to call the transform() function here
                
                // transform(prefs)
                // This call shows the error "suspension functions can be called only within coroutine context"
                
                return Transaction.success(currentData)
            }

            override fun onComplete(
                error: DatabaseError?,
                committed: Boolean,
                currentData: DataSnapshot?
            ) {
                if (error != null) {
                    continuation.resumeWithException(error)
                } else {
                    continuation.resume(currentData.toPreferences())
                }
            }
        })
    }
}

I found this similar question, but it doesn't really solve my problem because I can't call the transform function outside of doTransaction (I need currentData).

Also, I can't make transform a normal "non-suspend" function because I'm overriding that function from another class.

My question is: How can I apply the transform suspend function to currentData?

  • 1
    Non-suspendable functions can't wait in any other wait than by blocking. So if you can't anyhow redesign your code to perform this transformation outside of the callback (so before/after it), then I think the only possible way is by using `runBlocking()`. – broot Nov 08 '21 at 16:21
  • @broot Thanks, I'll consider using `runBlocking()` – Rosário Pereira Fernandes Nov 09 '21 at 06:44

1 Answers1

-1

I don't know exactly what your API is here, but maybe you can break this function up to do your transformation after the suspendCoroutine block so its being called inside the coroutine instead of in the API callback.

override suspend fun updateData(transform: suspend (prefs: Preferences) -> Preferences): Preferences {
    val retrievedPrefs = suspendCancellableCoroutine { continuation ->
        realtimeDatabase.runTransaction(object : Transaction.Handler {
            override fun doTransaction(currentData: MutableData): Transaction.Result {
                return Transaction.success(currentData)
            }

            override fun onComplete(
                error: DatabaseError?,
                committed: Boolean,
                currentData: DataSnapshot?
            ) {
                if (error != null) {
                    continuation.resumeWithException(error)
                } else {
                    continuation.resume(currentData.toPreferences())
                }
            }
        })
    }
    return transform(retrievedPrefs)
}
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • thanks for your answer, but unfortunately I can't do it after because the `doTransaction` method should write the transformed values to the database before returning Transaction success. – Rosário Pereira Fernandes Nov 09 '21 at 06:43
  • What database library is this? There may be at least a way to avoid having this `doTransaction` function being called on the main thread, because otherwise `runBlocking` presents a problem. – Tenfour04 Nov 09 '21 at 12:04