Networking on Android using Corutin and Retrofit

Original author: Joe Birch
  • Transfer
  • Tutorial

The more I read and watched reports about Korutin in Kotlin, the more I admired this language tool. Recently, Kotlin 1.3 released their stable release, which means it’s time to start diving and try out the Korutinas in action using the example of my existing RxJava-code. In this post, we will focus on how to take existing requests to the network and convert them, replacing RxJava with Cortutins.



Frankly, before I tried the Korutins, I thought they were very different from what they were before. However, the basic principle of korutin includes the same concepts that we are used to in RxJava jet streams. For example, let's take a simple RxJava configuration to create a network request from one of my applications:


  • We define the network interface for the Retrofit using the Rx adapter ( retrofit2: adapter-rxjava2 ). Functions will return objects from the Rx framework, such as Single or Observable . (Hereinafter, functions are used, not methods, as it is assumed that the old code was also written in Kotlin. Well, or converted from Java through Android Studio).
  • Call a specific function from another class (for example, a repository, or activate).
  • We define for the threads on which Scheduler they will be executed and return the result ( .subscribeOn () and .observeOn () methods ).
  • Save the object reference for unsubscribe (for example, in CompositeObservable).
  • Subscribe to the event stream.
  • Unsubscribe from the stream depending on the events of the Life cycle of the Activity.

This is the main algorithm for working with Rx (disregarding the mapping functions and details of other data manipulations). As for korutin, the principle does not change much. The same concept, only the terminology is changing.


  • We define the network interface for the Retrofit using the adapter for corutin . Functions will return Deferred objects from the Corutin API.
  • Call these functions from another class (for example, a repository, or aktiviti). The only difference is that each function must be marked as suspend .
  • Define the dispatcher that will be used for the quorutine.
  • Save the reference to the Job object for unsubscribe.
  • We start korutin in any available way.
  • We cancel coroutines depending on the events of the Activity life cycle.

As you can see from the above sequences, the process of performing Rx and Corutin is very similar. If we disregard the implementation details, this means that we can keep the approach that we have - we only replace some things to make our implementation coroutine-friendly.



The first step we need to take is to allow the Retrofit to return Deferred objects. Objects of type Deferred are non-blocking future, which can be canceled if needed. These objects are essentially a Coroutic Job, which contains the value for the corresponding job. Using the Deferred type allows us to mix the same idea as Job, with the addition of the ability to get additional states, such as success or failure - which makes it ideal for network requests.


If you are using Retrofit with RxJava, you are probably using RxJava Call Adapter Factory. Fortunately, Jake Wharton wrote its equivalent for Corutin .


We can use this call adapter in the Retrofit builder, and then implement our Retrofit interface in the same way as with RxJava:


privatefunmakeService(okHttpClient: OkHttpClient): MyService {
    val retrofit = Retrofit.Builder()
            .baseUrl("some_api")
            .client(okHttpClient)
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .build()
    return retrofit.create(MyService::class.java)
}

Now look at the MyService interface, which is used above. We must replace the Observable types returned by Deferred in the Retrofit interface. If it used to be like this:


@GET("some_endpoint")fungetData(): Observable<List<MyData>>

Now replace with:


@GET("some_endpoint")fungetData(): Deferred<List<MyData>>

Every time we call getData (), a Deferred object will be returned to us - an analogue of Job for requests to the network. Previously, we somehow called this function with RxJava:


overridefungetData(): Observable<List<MyData>> {
    return myService.getData()
        .map { result ->
            result.map { myDataMapper.mapFromRemote(it) }
        }
}

In this RxJava, we call our service function, then we apply a map operation from the RxJava API and then mapping the data returned from the request into something used in the UI layer. This will change a little when we use the implementation with coroutines. To begin with, our function must be suspend (deferred) in order to perform a lazy operation inside the function body. And for this, the calling function must also be deferred. The deferred function is non-blocking and can be controlled after it is initially called. You can start it, pause, resume or cancel.


overridesuspendfungetData(): List<MyData> {
    ...
}

Now we have to call our service function. At first glance, we do the same thing, but we must remember that now we get Deferred instead of Observable .


overridesuspendfungetData(): List<MyData> {
    val result = myService.getData()
    ...
}

Because of this change, we can no longer use the chain map operation from the RxJava API. And even at this point, data is not available to us - we only have the Deferred instance. Now we have to use the await () function to wait for the result of the query and then continue executing the code inside the function:


overridesuspendfungetData(): List<MyData> {
    val result = myService.getData().await()
    ...
}

At this point, we get the complete request and data from it available for use. Therefore, we can now perform mapping operations:


overridesuspendfungetData(): List<MyData> {
    val result = myService.getData().await()
    return result.map { myDataMapper.mapFromRemote(it) }
}

We took our Retrofit interface with the calling class and used the korutiny. Now we want to call this code from our Activity or fragments and use the data that we got from the network.


In our Activity, we’ll start by creating a Job reference, in which we can assign our coroutine operation and then use it to control, for example, cancel a request, during an onDestroy () call .


privatevar myJob: Job? = nulloverridefunonDestroy() {
    myJob?.cancel()
    super.onDestroy()
}

Now we can assign something to the myJob variable. Let's take a look at our request with corutines:


myJob = CoroutineScope(Dispatchers.IO).launch {
    val result = repo.getLeagues()
    withContext(Dispatchers.Main) {
        //do something with result
    }
}

In this post, I wouldn’t like to delve into Dispatchers or performing operations inside the Corutinos, as this is a topic for other posts. In short, what is happening here:


  • Create the CoroutineScope instance using the IO Dispatcher as a parameter. This dispatcher is used to perform blocking I / O operations, such as network requests.
  • We launch our corutin with the launch function — this function launches a new corutin and returns a reference to a variable of type Job.
  • Then we use the link to our repository to retrieve data by performing a network request.
  • In the end, we use the Main Dispatcher to do work on the UI thread. Here we will be able to show the received data to users.

In the next post, the author promises to dig deeper into the details, but the current material should be enough to start exploring the Korutin.


In this post, we replaced the RxJava-implementation of Retrofit responses to Deferred objects from the quorutin API. We call these functions to get data from the network, and then display them in our activity. I hope you saw how little change you need to make in order to start working with Corutinians, and appreciate the simplicity of the API, especially in the process of reading and writing code.


In the comments to the original post I found the traditional request: show the code in its entirety. Therefore, I made a simple application that, when it starts, gets a schedule of trains with the Yandex.Schedules API and displays it in RecyclerView. Link: https://github.com/AndreySBer/RetrofitCoroutinesExample


I would also like to add that cortinas seem to be an inferior replacement for RxJava, since they do not offer an equivalent set of operations for synchronizing threads. In this regard, it is worth looking at the implementation of ReactiveX for Kotlin: RxKotlin .


If you are using Android Jetpack, I also found an example with Retrofit, Korutin, LiveData and MVVM: https://codinginfinite.com/kotlin-coroutine-call-adapter-retrofit/


Also popular now: