Patnaik's Lab

Retrofit is a very useful tool, it lets us abstract calls to an server-side API in the form of functions. A lot of boilerplate code is avoided, while also reducing fragmentation in the source code. If you do not know Retrofit, please do visit https://square.github.io/retrofit/.

I have attempted to build a proof of concept of a tool similar to Retrofit in objective. It leverages the power of Kotlin extension functions and uses coroutines as the async mechanism (as it is gradually becoming the standard kotlin way). I use OkHTTP as the underlying mechanism and Moshi for serialization.

This is not a tutorial which builds the abstraction, but a conceptual one with reasons and approaches behind it. These concepts can be useful in something you would like to build.

Setup

You need to have an Android app and a SpringBoot server for this article.

Android

Create the app using Android Studio and add coroutines(https://github.com/Kotlin/kotlinx.coroutines) and okhttp(https://github.com/square/okhttp) as dependencies.

Server
For the Spring Boot server, either create by Intellij IDEA Ultimate, or use the following link to generate the project(https://start.spring.io/#!language=kotlin&type=gradle-project). Add spring web flux as a dependency to write a non-blocking IO http server.

Add kotlin coroutines using the instructions on the Github page.

Introduction

Spring Server

Here is the API written using SpringBoot.

https://medium.com/media/e67041c2eb93648f926fd58f0b6b8df7/href

As you can see, it is quite simple.

The server echoes the GET and POST requests with slight modifications

The /echo/{client}/{message} route handles a GET request. The client and the message portions of the route can be accessed inside the function using the “@PathVariable” annotation.

The /echo/{client} route handles a POST request. The body recieved in the request is accessible using the “@RequestBody” annotation.

Android (Retrofit)

https://medium.com/media/50990a7c11570c17cd62b5e822bcea71/href

Here is the client written in Retrofit. The similarity between the server and the client is really nice with retrofit.

Now this is the example with my abstraction layer

https://medium.com/media/d3124b85d4467d3f3d08f53167ccc519/href

It is also simple. At the end of this article, you will understand how to build this, or similar things.

Both of these can be called in the following manner

https://medium.com/media/840447cbae3990ce2641cb64852174f7/href

Kotlin concepts used here (brief)

Abstract Classes

An abstract class is nothing but a template for writing your own classes with shared logic, while not allowing objects to be created. This is useful to write some common behaviour which depends upon some actions which will vary according to usage. Here, abstract classes will represent the basic API template and expose some functions to create the calls.

Inline Functions

These are special functions that the compiler will optimise away. Instead of making a call to this function, the compiler will instead bring the instructions inside the function to the site where the function is called. This helps increase performance and also has some areas where normal functions can not be used.

Generics

A generic function or class accepts another class/type as a parameter. This allows us to write code that can work on multiple classes of objects with one implementation.

Reified Types

These are special types meant for use inside inline functions only. They allow us to inspect classes passed to functions without resorting to reflection. Now, we can’t inspect all classes, but those that are known at compile time. https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters

Extension Methods

These are special methods you can declare for classes that we do not own (we didn’t write or do not have write access). This allows us to write helper functions which would have been previously written as static methods in Java without having to resort to weirder ways to reducing code clutter.

Coroutines

Coroutines is a vast topic and deserves multiple articles on its own. But you can think of them as light weight threads that are run on predefined threadpools and jobs represented as functions that can suspend execution.
You have multiple Dispatchers (threadpool wrappers) to which you delegate tasks, and you have mechanisms for transferring data in between these thread pools.

To be concrete, we start the blocking network call in a withContext(Dispatchers.IO) block. (In android, you can’t make a network call on the main thread, but making a call here is fine since it is done on the IO thread pool)

Now we can’t call this function from the main thread(it is running on a different thread), but instead we call the function using viewModelScope.

Domain Specific Language (DSL)

These are usecase focused languages, which are used to write logic declaratively instead of imperatively.
DSLs can vary from basic languages like the one we will be writing for this one to highly advanced ones like SQL.
Kotlin is an excellent language for constructing DSLs.
A lot of repeated code that can be error-prone is a good candidate for DSL replacement.
To be specific, for OkHTTP, we create the URL then the Request with some query parameters. Then we supply this request object to the newCall function and then call execute.
To do this declaratively means that we only specify the requirements and the code to achieve this is not written multiple times.
Retrofit uses annotations to achieve this, With kotlin we will do this using DSL.
The primary tool we need from Kotlin is the Lambda function (specifically lambda with a receiver)

The primary tool we need from Kotlin is the Lambda function (specifically lambda with a receiver)

Entire Code (explanation after code)

https://medium.com/media/e139be45dc247335778b693614619026/href

It is not meant to be understood in one go. I believe in learning the skeleton of something before diving into the parts in order to get a better understanding. When we see something for the first time unassisted, our brain automatically searches for information and patterns. This helps us to gain greater insight when we then go into depth.

I will be dividing this into four portions

  1. Evaluating Strategies
  2. Creating the representation
  3. Creating the request and making a call
  4. Writing the JSON adapter logic
  5. Writing the GET function (POST is similar)

Evaluating Strategies

These are few important points about an API from the perspective of building a representation for it.

  1. An API offers different endpoints to perform different tasks.
  2. All endpoints should be independent at client side with respect to state.
  3. Request/Response types and parameters can vary widely

We can write free functions for each endpoint and that won’t be a bad thing to do, but in my opinion, a logical group of things should be grouped together inside the program.

To group functions representing endpoints, we have three options.
Interface
Requires mechanism to create implementation such as annotations at build time, and reflection at runtime.
This is Retrofit’s choice and is good.

Class
To call the API, we need to construct an object of that class. Multiple objects for representing an API generally does not make sense. We have some methods to create singleton objects, but in my opinion writing classes and this logic for each API is an overkill.

Object
Kotlin has singleton objects that are a good base for creating this abstraction without having to write a lot of additional logic. Implementing helper functions to create the API should be all that is required. And as we will see, that is true.

Creating the representation

Let us first have a look at a toy implementation.
Our API makes no calls and just returns strings based on payloads.
But this showcases how we use abstract class, helper functions and the special function equals syntax to write declaratively.

https://medium.com/media/4d6549f711a61e508460c7f6346fae3f/href

This looks and is stupid, so we will follow on with a stupider version that looks like what we want, but does nothing.

https://medium.com/media/40d0787e9cfa7c99fdd0b21c0f029ca3/href

We have declared few classes to store the information that varies between endpoints and are used to modify the default behaviour such as headers and query params.

To create objects we are using Lambda with recievers instead of the constructor because we want to be able to write additional logic if needed.

This looks like a syntax that we can settle with, but of course it is useless right now without any actual networking code. So let’s get started.

Creating the request and making a call

https://medium.com/media/754a382e2f9e85227e1aa94a7d8b7311/href

For a given string message and server host, port; this is how we build the GET request.

If we occur an error, or if we have an API failure we send null otherwise we send the response body as a string.

Writing the JSON adapter logic

https://medium.com/media/f97d85845cc4b1101632bfa90273e08b/href

Here we use reified generic type because our Response type is different for each request but is known at compile time, so it allows us performance improvements by not using Reflection.

We basically create a JSON adapter, make the call and convert the JSON into an object.

Writing the GET method

https://medium.com/media/bc6c7ad1ae311e8cc87ef23e47b69895/href

First we write the blocking version of the final function.

Steps
1. Create a Moshi adapter to use for JSON to Object transformation.
2. Create a GETModifier and apply the function that we have sent.
3. Create the URL and the Request objects.
4. Make the call and get the response body (as a string)
5. Convert the possibly null (from error) body to the object and return it.

This is almost near the real one, which needs additional logic to make it non-blocking on main thread (blocks on IO thread pool) and a suspend function.

https://medium.com/media/cc5dd5fb9365fba4bced8ef15ad5d343/href

Now, please have a second look at the original code containing all of these parts. They will make a lot more sense now.

I believe Kotlin is a truly great language and hope to create more things with it.
Now go build something you want to.