A Solution for Combining Kotlin Suspend Function + Retrofit | by ZhangKe | Jun, 2022

A brief guide to simplifying your functions

Photo by Chad Stembridge on Unsplash

Before Kotlin coroutines, when we used Retrofit, we usually used it in conjunction with RxJava to represent a created request through Single.

Single in RxJava represents a stream containing only one event, so it is relatively reasonable to use it as a network request.

However, since it is an HTTP network request, the return value must be only one time, or an exception is thrown. It seems inappropriate to describe it as a stream. After all, a stream composed of a single event sounds strange.

Therefore, for functions in the form of initiating network requests, it is more practical to return data directly or throw exceptions.

So for Kotlin, this kind of function is the suspend function. And Retrofit has also supported the suspend function.

Let’s take a look at what the network request of suspend + Retrofit looks like.

This seems to be more intuitive, calling the request function directly returns the response data, or throws an exception.

But it still seems to be a bit problematic, a bit verbose, and it has to write try-catch, which is not simple and elegant enough.

In fact, it can also be replaced with Flow, but it still feels not the best solution. The design of Flow is not suitable for use here, and there must be a better solution.

Some time ago, I encountered this problem when I switched to Kotlin coroutines. At that time, I thought of a solution that looked good and shared it here.

The problem to be solved at present is that we expect to use the suspend function to initiate network requests and return response data directly, but at the same time do not want to write try-catch.

My idea is to build a response class to wrap the data and exceptions after Retrofit gets the request, and the caller gets the data or exceptions through this class. Others can still follow the previous logic, and the writing method is similar to the following:

Response is the wrapper class mentioned above, and the onSuccess and onError code blocks correspond to success or failure, which looks much simpler than before.

Here’s how to do it.

Since you need to add a return type to Retrofit, you need to create a CallAdapterFactory corresponding to Response first. Construct a Response after obtaining the response data or exception, and store the data or exception in it. This is generally the idea, which is relatively simple. Then we will take a look at the specific implementation step by step.

Let’s first look at how the Response mentioned above is defined.

This is relatively simple, success indicates whether the request was successful or not, which is assigned externally after the Response is constructed. The generic S represents the data type returned after the request is successful, and the generic E is the type of the request successful but the interface returns the wrong data.

ErrorResponse is defined as follows.

Considering the friendliness to the caller, the error data is limited to at least one errorMessage.

First, create a ResponseCallAdapterFactory that extends CallAdapter.Factory and implements the get method.

Retrofit will call this method to try to get a CallAdapter of the corresponding type, and use it to construct the final return value.

In this get method, you need to first determine whether it is the return type. We need to deal with the input parameters, otherwise, return null.

The above code mainly judges whether it is a suspend function and whether the return value is the Response we mentioned above. In addition, you need to get the specific Response type (Response is designed to be inheritable) and the generic type.

The code comes here according to the above logic, which means that we have encountered a function that we need to process. At this time, we need to combine the types obtained above to create and return the corresponding CallAdapter object.

The ResponseCallAdapter returned above is also the class we created.

This class is mainly used to determine the processing type and build a Call object.

ResponseCall needs to inherit from Call and set the generic type.

Most of the methods can be delegated to the delegate object, where execute will throw an exception directly, because here is the suspend function.

The main logic is still in the enqueue function.

The enqueue method of the delete method is still called first. The Callback will contain the retrofit2.Response of the end of the request, and we will get it to continue building our Response.

In addition, in general, the interface data type of service is agreed and unchanged, that is, all response data have the same ErrorEntry. Then for convenience, you can define a specific service Response, and the Error generic type is this ErrorEntry for the service. This is why the above Response is designed to be open.

For example, Notion’s public API has the same Error type.

We can define a NotionResponse for the Notion service.

When I was learning Compose some time ago, I took the Notion API and made an open source project called NotionLight (a lightweight Notion client). The content of this article is also when I was working on this project. The problems encountered, all the above code, and specific use cases are also in this open source project.

Want to Connect?Here's my GitHub to see the specific code.

News Credit

%d bloggers like this: