strok_777
strok_777

Reputation: 1341

Convert Callback to suspendable coroutine in Kotlin

I currently have a callback that instead of returning a value synchronously, it will need to do some async work and then return the result. I've been investigating about how to do it but I don't get thing clear. What I have achieved until now is this, but as I need to put a return into foo method, at first it returns "something" and afterthat return the result of calling someMethod().

Is it possible to to what I'm pretending?

  class CustomClass() :
    SomeClass.SomeInterface {

    override fun foo(p0: SomeType): String {
      val result = "something"
      MainScope().launch {
        withContext(Dispatchers.IO) {
          result = someMethod()
        }
      }
      return result
    }

    suspend fun someMethod(): String =
      suspendCancellableCoroutine { cancelableContinuation ->
        //TODO: register some listener that will end up calling 
        cancelableContinuation.resume("some_value", {})
      }

Thanks in advance!

Upvotes: 5

Views: 7560

Answers (2)

Tenfour04
Tenfour04

Reputation: 93609

You cannot return the result synchronously, so foo has to be defined as a suspend function.

Also, there's no reason to wrap a suspend function call in withContext. Suspend functions (if composed correctly) internally wrap any blocking code so they are safe to call from any dispatcher, so there's no reason to specify one using withContext

If you strip away the withContext and the launching of the coroutine that you weren't waiting for, foo() does nothing besides calling someMethod, so you can just move that code from someMethod into foo. But foo must be defined as a suspend function.

override suspend fun foo(p0: SomeType): String = suspendCancellableCoroutine { continuation ->
    val request = someApi.request(p0) { result: String ->
        continuation.resume(result)
    }
    continuation.invokeOnCancellation {
        request.cancel()
    }
}

Generally you don't want model classes like this launching coroutines from their own CoroutineScopes. Just have them expose suspend functions. It's up to the higher level classes like Activity, Fragment, and ViewModel to launch coroutines from their desired scopes so they can control the coroutine lifecycles.

Upvotes: 4

Róbert Nagy
Róbert Nagy

Reputation: 7612

Your foo function is basically synchronous, so what's happening is that you launch a suspendable don't wait for it's result and return the initial value of result ("something")

You have multiple options for making sure, foo waits for the response from someMethod, for ex:

  • Make foo a `suspend fun
  • Use a callback, for example:
override fun foo(p0: SomeType, onResult: (String) -> Unit): String {
      MainScope().launch {
        withContext(Dispatchers.IO) {
          onResult(someMethod())
        }
      }
    }

// Call it like this:
foo(p0) { result ->
    // handle result
}

Upvotes: 1

Related Questions