Kotlin Coroutine Collaboration with Retrofit (KCCR)

It's been a while since I last blogged about Android in general and a lot has changed since my last article about Retrofit which I published about 2 years ago. If you're a complete beginner then this is a must read, you might enjoy it ;). ahem,


Okay, maybe it's not that great, but you have to admit that Java has done a good job as Android's primary development language.

Kotlin and now coroutines make networking on Android easier than ever!

Nowadays Asynchronous Programming is a hot topic to use in Android projects. To avoid doing heavy tasks on the Main thread to improve the performance of API calls and data manipulation in the UI part Coroutine and Retrofit is one of the best combinations.

Community Interest Statement

All the coroutine videos and blog posts are crappy and miss important details. GlobalScope and runBlocking are not allowed, from the docs, so how do you launch a coroutine? Launch and Async require an existing coroutine, so it gets really confusing when almost all the examples out there use runBlocking from main or something, GlobalScope, or suspend fun main.

Let's say I create a suspend function, oh another thing that is said to be avoided! Maybe I use a library that has a suspend function.

I want to call this suspend function from fun main without using runBlocking or GlobalScope, is there an example out there in the form of a tutorial or video that actually shows this?

I thought I needed a scope with the work and then I could launch it, why did they miss such an important detail.

Not using suspend fun main either, how do you start a coroutine somewhere in your app from Kotlin code.

In my experience, people on the internet overreact with this "Don't use GlobalScoper/runBlocking()!`. Then they often give alternatives that do the same thing as above, so it seems they don't understand why these components are discouraged.

Both have their uses, neither is evil or anything. But it's important to understand their drawbacks, as they're easy to overuse.

runBlocking()

Use it when you need to bridge regular code and suspending code synchronously, by blocking the thread. Just don't overuse it. Don't treat runBlocking() as a quick fix for calling a suspending function. Always think about whether you shouldn't propagate the suspension to the caller. Or use futures/callbacks. Or separate the regular world and the suspending world and leave a little bit of a connection between the two. If you start adding runBlocking() everywhere in your code then this is a bad use.

GlobalScope

Use it if you don’t care about structured concurrency. If you need to launch some coroutines that you will never cancel, you won’t check their status, you don’t care about their failures, etc. then GlobalScope is fine (in fact, even when using GlobalScope we can still store Jobs and handle failures). However, it’s worth taking a moment to think about whether you really don’t care about all of this. It’s annoying and sometimes hard to debug if one asynchronous task fails and the others go crazy.

Implement some kind of service with a clear lifecycle and create a CoroutineScope for it. This is explained here 267 . This bridges the traditional way of running asynchronous services reliably with the structured concurrency of coroutines.

In general, it is more complicated than other concurrency tools/frameworks (including threads) simply because coroutines try to fix the problem of asynchronous task cancellation and failure. It is generally discouraged to “just start a coroutine”, because we do not control its state. We are encouraged to explicitly choose what should happen in exceptional states.

Using Coroutines with Scope in Multiple Classes

How to provide scope or how to call suspend function from Service or Other Static Class in Android? Usually, Activity (CoroutineScope(Dispatchers.IO) or withContect(Dispatchers.Main)) or ViewModel (viewModelScope.launch(Dispatchers.IO)) provides scope to launch suspend function, but Service and other Static Class are different.

Solution

You can create your own CoroutineScope with a SupervisorJob that you can cancel inside the onDestroy() method . Coroutines created with this scope will run as long as your service is used. Once your service's onDestroy() is called, all coroutines started with this scope will be canceled.

class  YourService : Service() {

    private  val job = SupervisorJob()
     private  val scope = CoroutineScope(Dispatchers.IO + job)

    ...

    fun  foo () {
        scope.launch {
            // Call your suspend function
        }
    }

    override  fun  onDestroy () {
        super.onDestroy()
        job.cancel()
    }
}

Retrofit Response<> vs Call<> Return Values

I want to know the difference between Call and Response? When to use Call & when to use Response in Retrofit?

@GET( "/albums/{id}" )
suspend fun  function One (@Path(value = "id" ) albumsId:Int):Response<Albums>
@GET( "/albums/{id}" )
suspend fun  function Two (@Path(value = "id" ) albumsId:Int):Call<Albums>

Both of these functions work fine for me, they both have different implementations but serve almost the same purpose. 

  1. What type of response is good for best practice?
  2. When to use Response & Call?

Solution

Call is used when we want to use Retrofit's built-in async mechanism by utilizing its callback, for example:

call.enqueue( new Callback<Info>() {
            @Override
            public  void  onResponse (Call<Info> call, Response<Info> response) {

                Info info = response.body();

                if (info != null && info.getRestResponse() != null ) {

                    results = (ArrayList<Result>) info.getRestResponse().getResult();
                    viewData();
                }
            }

            @Override
            public  void  onFailure (Call<Info> call, Throwable t) {

            }
        });

It's a different story when you want to combine Retrofit with native Coroutine, because what Retrofit does is the old Java way, but that's good enough, however, there's something more revolutionary, Kotlin introduces a friendlier way of managing multi-threading, namely with Coroutine.

interface RetrofitService {
    @GET( "/posts" )
    suspend fun  getPosts (): Response<List<Post>>
}

Then in ViewModel you can suspend function in the following way:

class  MainActivity : AppCompatActivity() {

    override  fun  onCreate (savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val service = RetrofitFactory.makeRetrofitService()
    CoroutineScope(Dispatchers.IO).launch {
        val response = service.getPosts()
        withContext(Dispatchers.Main) {
            try {
                 if (response.isSuccessful) {
                     //Do something with response eg show to the UI. 
                } else {
                    toast( "Error: ${response.code()}" )
                }
            } catch (e: HttpException) {
                toast( "Exception ${e.message}" )
            } catch (e: Throwable) {
                toast( "Ooops: Something else went wrong" )
            }
        }
    }
}
}

With the same pattern, you can also peel it off in ViewModel simply using  viewModelScope.launch{} no need to declare Dispatchers.

About Coroutine Builder

As an additional insight that there are several coroutine builders that you can utilize as appropriate. Please see here to learn more about it.

Reference:

  • https://discuss.kotlinlang.org/t/globalscope-and-runblocking-should-not-be-used-so-how-do-you-start-a-coroutine/24004/8
  • https://stackoverflow.com/questions/63405673/how-to-call-suspend-function-from-service-android
  • https://medium.com/android-news/kotlin-coroutines-and-retrofit-e0702d0b8e8f
  • https://stackoverflow.com/questions/64124670/call-or-response-in-retrofit

Post a Comment

Previous Next

نموذج الاتصال