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 corutines.
Frankly, before I tried the korutiny, I thought they were very different from what it was 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:
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.
As you can see from the above sequences, the process of performing Rx and Corutin is very similar. If we ignore the implementation details, this means that we can maintain 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:
private fun makeService(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") fun getData(): Observable<List<MyData>>
Now replace with:
@GET("some_endpoint") fun getData(): 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:
override fun getData(): Observable<List<MyData>> { return myService.getData() .map { result -> result.map { myDataMapper.mapFromRemote(it) } } }
In this RxJava, we call our service function, then we apply the 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. A deferred function is non-blocking and can be controlled after it is initially called. You can start it, pause, resume or cancel.
override suspend fun getData(): List<MyData> { ... }
Now we have to call our service function. At first glance, we do the same thing, but we need to remember that now we get Deferred instead of Observable .
override suspend fun getData(): 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, the 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:
override suspend fun getData(): 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:
override suspend fun getData(): 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.
private var myJob: Job? = null override fun onDestroy() { 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:
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 have replaced the RxJava-implementation of Retrofit responses to Deferred objects from the API quorutin. We call these functions to get data from the network, and then display them in our activations. I hope you saw how little change you need to make in order to start working with the corints, 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 the schedule of trains with the Yandex.Rasks API and displays it in RecyclerView. Link: https://github.com/AndreySBer/RetrofitCoroutinesExample
I would also like to add that cortinas seem to be an inadequate replacement of 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/
Source: https://habr.com/ru/post/428994/
All Articles