Skip to main content

Building your own Retrofit Call Adapter

The use of RxJava has really shielded us away from topics like this because RxJava is now a ubiquitous term with android development, so there is almost no reason to adapt your retrofit responses since we rely on Rx observables for most apps.
I recently had to write a library that required retrofit for network requests, and per the request of the team lead, we were not to include Rx. This was kind of a shocker because I realized I would have to go back to passing interface callbacks to my asynchronous requests, for example:
httpService.getUsers(12).enqueue(object : Callback<R> {

    override fun onFailure(call: Call<R>?, t: Throwable?)  {
        // handle error
    }

    override fun onResponse(call: Call<R>?, r: Response<R>?) {
        // handle success
    }

})
I wasn’t going to do this, so I decided to go on the journey of writing my own simple call adapter that will just provide the response and throwable for a single callback function, just like with Rx subscribe method.
Here are the things you need to do to achieve that.

Transforming the retrofit Call object

The original way of providing responses, without any adapters, is using the retrofit Call object
interface IHttpService {

    @GET("users")
    fun getUsers(@Query("results") result: Int): Call<List<Users>>

}
and what we are trying to achieve is, transform this call object to provide our own wrapper, for the response
interface IHttpService {

    @GET("users")
    fun getUsers(@Query("results") result: Int): Simple<List<Users>>

}
notice that the generic wrapper is no longer call but simple. It is this simpleobject that will be responsible for issuing out the request and providing a compound result of error and response so that you can use it like this:
httpService.getUser(12).process { users, throwable ->
    // handle error or response
}
That process function that you see above, is a member of the Simple class and will be responsible for issuing out the request and invoking your callback.

Defining the Simple Transformer

We need to define the simple class so that it performs both asynchronous and synchronous network calls using the call object. That would look like this:
class Simple<R>(private val call: Call<R>) {
  fun run(responseHandler: (R?, Throwable?) -> Unit) {
    // run in the same thread
    try {
       // call and handle response 
       val response = call.execute()
       handleResponse(response, responseHandler)

     } catch (t: IOException) {
        responseHandler(null, t)
     }
   }

  fun process(responseHandler: (R?, Throwable?) -> Unit) {
    // define callback
    val callback = object : Callback<R> {

      override fun onFailure(call: Call<R>?, t: Throwable?) = 
        responseHandler(null, t)

      override fun onResponse(call: Call<R>?, r: Response<R>?) = 
        handleResponse(r, responseHandler)
     }

    // enqueue network call
    call.enqueue(callback)
  }
private fun handleResponse(response: Response<R>?, handler: (R?, Throwable?) -> Unit) {
    if (response?.isSuccessful == true) {
         handler(response.body(), null)
    } else {
      if (response?.code() in 400..511) 
        handler(null, HttpException(response))
           
       else handler(response?.body(), null)
        
     }
  }
}
Now let’s talk about this class, and there are a few points to note:
  • First, it takes a call object in its constructor, which is the object containing the HTTP request to be issued out.
  • The run method is used to synchronously issue out network requests, and pass results to the response handler
  • The process method is used to asynchronously issue out network requests, and pass the results to the response handler
so with this class set as your return type for your network request, you can do this:
// run synchronously
httpService.getUser(12).run { users, throwable ->
    // handle error or response
}
// run asynchronously
httpService.getUser(12).process { users, throwable ->
    // handle error or response
}

Creating the Call Adapter

To let retrofit know of this transformer we defined above, we have to create an implementation of the retrofit CallAdapter interface like so:
class SimpleCallAdapter<R>(private val responseType: Type): CallAdapter<R, Any> {

    override fun responseType(): Type = responseType

    override fun adapt(call: Call<R>): Any = Simple(call)
}
From the code above you see that the Call Adapter is responsible for two things:
  • Providing a Type object that will be the type of the request’s response, that is, the type of the object in the HTTP response body. (i.e. ‘List<User>’ inSimple<List<User>>)
  • providing the transformer that will transform the retrofit call object:which is what we had defined earlier (Simple<R>).

Defining the Call Adapter Factory

The last thing we need to do is let retrofit know of this call-adapter and to that, we use a CallAdapter.Factory implementation. This will be defined as like:
class SimpleCallAdapterFactory private constructor() : CallAdapter.Factory() {

    override fun get(returnType: Type?, annotations: Array<out Annotation>?, retrofit: Retrofit?): CallAdapter<*, *>? {
        return returnType?.let {
            return try {
                // get enclosing type
                val enclosingType = (it as ParameterizedType)

                // ensure enclosing type is 'Simple'
                if (enclosingType.rawType != Simple::class.java) 
                    null
                else {
                    val type = enclosingType.actualTypeArguments[0]
                    SimpleCallAdapter<Any>(type)
                }
            } catch (ex: ClassCastException) {
                null
            }
        }
    }

    companion object {
        @JvmStatic
        fun create() = SimpleCallAdapterFactory()
    }

}
We see that this Factory is meant to implement only one method, get. This getmethod takes a couple of parameters, which I will explain shortly, and it must return an implementation of CallAdapter.
You can ignore the nullable types in the parameter list, that is just a result of Kotlin and Java interop. But here is what each parameter is:
  • return type: This is the return type of the calling function, for instance, the method getUsers of IHttpService, is Simple<List<User>>
  • annotations: This is a list of annotations that the calling method has, for instance, the method getUsers of IHttpService, will have one annotation, which is the retrofit GET annotations.
  • retrofit: This is the retrofit instance that created the http-service containing the calling method.
Now let us talk about what’s going on inside the function. Since the function is meant to return an implementation of CallAdapter, you guessed it, we return our defined SimpleCallAdapter. But before we do that we need to use the parameter to determine whether we return our CallAdapter or null.
That right, null. This is done like this because the retrofit instance can contain multiple call adapter-factories for different call adapters, so we return nullwhen the return type is not our concern (i.e. it is not a ‘Simple’ type)
This is precisely what the function is doing. It checks that the parameterized type is a Simple type, and then creates our simple adapter with the type parameter argument as the response-type, else it returns null.
The last thing of note is our static create function in the companion object, which is responsible for creating an instance of the adapter factory.

Finally adding the Factory to Retrofit

This part is the simplest, all you need to do is call the static create function of the Factory and add that instance to retrofit, using the retrofit builder like so:
Retrofit retrofit = new Retrofit.Builder()
        //... other configs
        .addCallAdapterFactory(SimpleCallAdapterFactory.create())
        .build();
and that is it, now retrofit will use this Adapter Factory to create an Adapterand use the created Adapter to transform the Http Call object.

Other Notes

There are two other concerns that you need to be aware of :
  • The first is support for Java using a functional interface so that Java devs don’t have to write ugly Kotlin Function classes.
  • The second concern is, the beauty for RxJava adapters is the ability to cancel subscriptions when the response is no longer needed. The current implementation doesn’t have this.
These two concerns can be addressed ass follows:
First, create an interface for Java devs and add that to overloaded methods like this:
// create functional interface
public interface SimpleHandler<T> {
    void accept(T response, Throwable throwable);
}
Add overloaded methods to the Simple transformer, that will require this interface as a callback:
class Simple<R>(private val call: Call<R>) {

    // support for java
    fun run(responseHandler: SimpleHandler<R?>) =
               run(responseHandler::accept)
    fun process(responseHandler: SimpleHandler<R?>) =    
               process(responseHandler::accept)
    //... other members
 
}
The second problem is providing a way to cancel subscriptions, to prevent a callback method from being called. This is applicable to only asynchronous functions, thus the only part we need to be concerned about is the processfunction:
First let us create a Subscription class that can hold a shared state:
class Subscription {

    private var disposed = false

    fun isDisposed() = disposed

    fun dispose() {
        disposed = true
    }
}
We will now use this class to share the state of subscription between the process function and the enclosing class. We do this by creating a subscription and returning it from the process function:
class Simple<R>(private val call: Call<R>) {

    //  ... other memebers
    fun process(responseHandler: (R?, Throwable?) -> Unit): Subscription {

        val subscription = Subscription()

        // define callback
        val callback = object : Callback<R> {

            override fun onFailure(call: Call<R>?, t: Throwable?)  {
                if (!subscription.isDisposed()) 
                    responseHandler(null, t)
            }

            override fun onResponse(call: Call<R>?, response: Response<R>?) {
                if (!subscription.isDisposed()) 
                     handleResponse(response, responseHandler)
            }

        }

        // enqueue network call
        call.enqueue(callback)
        // return subscription
        return subscription
    }
    // ... other memebers
}
Then in your calling class, you can do this:
val subscription = httpService.getUsers(12).process { r, t ->
    // handle success or response
}

// dispose subscription
subscription.dispose()

Conclusion

In this post, I have shown you how to:
  • Create your own Call transformer
  • Create your own CallAdapter
  • Create your own CallAdapter.Factory
  • Make retrofit aware of your Call Adapter
That is it, you can check out my github repo.

Comments

Popular posts from this blog

web2apk

http://web2apk.com/create.aspx Create App   Intro   About   Changes   MalWare ?   Contact   Privacy Useful Links Bluetooth Mini Keyboards Android Mini PC Reset Android URL App Title Icon or

Android Bar Chart Using MpAndroidChart Library Tutorial

https://www.numetriclabz.com/android-bar-chart-using-mpandroidchart-library-tutorial/ Android Bar Chart Using MpAndroidChart Library Tutorial Objective In this tutorial we learn how to implement Bar Chart using MpAndroidChart Library in your Android App. Download Source Code       Step 1 Contents ·        1  Introduction ·        2  Creating Bar chart o    2.1  Create a new Project o    2.2  Adding library in Project o    2.3  Create Layout o    2.4  To Plot Bar Chart §   2.4.1  Initialize the graph id §   2.4.2  Creating a Dataset §   2.4.3  Defining X-axis labels §   2.4.4  Set the data §   2.4.5  Add the description to the chart §   2.4.6  Run your App § ...

how to retrieve image from sqlite database in android and display in listview

 Android platform provides several ways to store data in our application. 1. SQLite database 2. SharedPreferences etc For our post, we will only work with SQLite database. First and foremost, we need to understand what an SQLite database is? SQLite database  is an open source SQL database that stores data to a text file on a device. It executes SQL Commands to perform a set of functions, that is, create, read, update and delete operations. On my previous post, I showed how to  store data in SQLite database from edit text, retrieve and populate it in a listview . For this post, I will show the SQLite CRUD operations with images from gallery and text from EditText. We need to understand this; images are stored in SQLite database as BLOB data type. A BLOB is a large binary object that can hold a variable amount of data.  Note, we can only store images in the database as BLOB data type. We need to convert our image path to a bitmap th...