Search code examples
kotlinretrofitlaunchcoroutinescope

Correct use CoroutineScope with retrofit api call


Is this correct Coroutine and retrofit use? i have some problems with interception of service (RuntimeException in geting getAccessTokenBlocking via intercept), and maybe it due to incorrect CoroutineScope use?

override fun onBindViewHolder(...){
val service = serviceBuilderFactory
            .create(serviceEndpoint, acc)
            .buildWithCache(Service::class.java)

CoroutineScope(Dispatchers.IO)
            .launch(exceptionHandler) {
                val obj = service.getObj() 
    //should i use here for webView.post - withContext(Dispatchers.Main) {  ??????
   // or shoud i use async and resume webView.post after callback?

                webView.post {
                    webView.loadUrl(obj.url)
                    }
             }
}

retrofit

@GET("/url")
suspend fun getObj(): Obj

Solution

  • This shouldn't be in an adapter at all. You're firing off coroutines every time an item scrolls onto the screen, and since you create a one-off CoroutineScope to launch each one, you have no means of cancelling them. If the user rotates the screen a couple of times quickly, you'll have like 30 obsolete coroutines running in the background that you can't cancel, and many of them will be doing redundant work.

    Instead, you should do the fetching in a ViewModel so the fetches don't have to be repeated redundantly when the screen is rotated. And use viewModelScope to launch the coroutines so they'll be automatically cancelled when they become obsolete from the current screen going out of scope.

    If you don't mind pre-fetching each item's data before showing it in the RecyclerView, you can map your data type to include the fetched URL before you even expose it to your Activity/Fragment via a LiveData or Flow.

    If you want to lazily start loading the URL only when the item appears on screen, you can map your data type to a Deferred<String> using async(start = CoroutineStart.LAZY) { ... }. Then add a CoroutineScope parameter to your adapter's constructor so the Activity can pass lifecycleScope or Fragment can pass viewLifecycleScope, and you can use that scope to launch coroutines in the adapter that will automatically be cancelled when obsolete. And you can use these coroutines to await() the deferred URL.