Search code examples
androidkotlincoroutinekotlinx.coroutines

AndroidNetworking never returns when in suspendCoroutine in Kotlin


I am experimenting with the new coroutines and trying to incorporate them into an existing project with some ugly AndroidNetworking api requests.

So my current api requests are using callbacks like this...

fun getSomethingFromBackend(callback: (response: JSONArray?, error: String?) -> Unit) {
  AndroidNetworking.get(...).build().getAsJSONObject(object : JSONObjectRequestListener {
    override fun onResponse(response: JSONObject) { ... }
    override fun onError(error: ANError) { ... }
  }
}

And now I am trying to incorporate coroutines like this...

fun someFunction() = runBlocking {
  async { callGetSomething() }.await()
}

suspend fun callGetSomething(): JSONObject? = suspendCoroutine {
  Something().getSomethingFromBackend {something
    ...
    it.resume(something)
  }
}

As you can see, I am trying to run the callGetSomething in a coroutine and once there, suspend the coroutine until the async API returns.

My problem is, it never returns. Even if I debug in the onResponse, onError, etc, it never get back from the backend.

But if I remove the = suspendCoroutine it works, at least it returns, but the await will not work off corse, because the execution will just continue.

How can I solve this so the await() actually waits for the async call callback?

Thanks a lot!


Solution

  • Ok, thank you all for your help. I'll explain how I found the problem and how I was able to solve it.

    After studying more about callbacks and coroutines I decided to log the thread name during the call back, like this. Log.d("TAG", "Execution thread: "+Thread.currentThread().name).

    This lead me to the realization that all the call backs (error and success), where actually running on the main thread, and NOT on the coroutine I set for it.

    So the code in my coroutine runs in D/TAG: Execution thread: DefaultDispatcher-worker-1 but the callbacks runs on D/TAG: Execution thread: main.

    The solution

    1. Avoid runBlocking: This will block the main thread, and in this case this was the reason for the callbacks never returning.

    2. Use the Dispatchers.Main when starting the coroutine, so I can actually use the returned values in the UI.

    3. Use a suspendCoroutine with it's resume() and resumeWithException (Thanks @Sergey)

    Example

    fun someFunction() {
      GlobalScope.launch(Dispatchers.Main) {
        result = GlobalScope.async { callGetSomething() }.await()
        ...
      }
    }
    
    suspend fun callGetSomething(): JSONObject? = suspendCoroutine {
      Something().getSomethingFromBackend {something
        ...
        it.resume(something)
      }
    }