Write Your Android Networking as a Kotlin DSL

Photo by Aman jha from PexelsWrite Your Android Networking as a Kotlin DSLBrandon WeverBlockedUnblockFollowFollowingFeb 20One of the features of Kotlin I’ve been playing with lately is creating DSLs.

DSL stands for Domain Specific Language, a way of writing code to solve a specific problem.

DSLs have the advantage of being declarative, concise, and fairly easy to understand what is happening.

The Kotlin documentation has a great example of using Kotlin’s type-safe builder functions to create HTML as a DSL.

As an Android developer, I’m fairly happy with the power that Retrofit provides but I love experimenting so I’m wondering if we can build something that’s maybe a bit more beginner friendly, has less set up, and is still powerful and easy to understand.

I’m not promising you can use this approach in your next app, but it’s a fun thought experiment anyway.

What we want our code to look likeI’m going to write this article a bit backwards, since it’s how I worked when I put this sample together.

I had an idea of what I was going for and worked towards that.

The basic idea of what I want to accomplish is to make a network request using OkHttp in a declarative fashion, without losing any of the power of the library or writing too much boilerplate.

What we’re trying to accomplishLike I said previously, we’re probably not going to beat the API design of Retrofit, but I think if we can get the above to work it should make a pretty nice repeatable pattern to create our services.

Getting Started with ExtensionsExtension functions are a little bit of syntactic sugar that Kotlin provides that allows developers to add functionality to classes without altering the code source.

Since we can’t really put in a PR to OkHttp to add the features that would only work for us, we can use extension functions to get the job done.

We’ll start by tackling the synchronous execute function.

Normally the execute function is on the Call object, but for the above code to work it looks like the the execute function is on the OkHttpClient object.

In order to accomplish this we’ll add the following code:class ExecuteFields { lateinit var request: Request}@Throws(IOException::class)fun OkHttpClient.

execute(init: ExecuteFields.

() -> Unit): Response { val executeFields = ExecuteFields().

apply(init) return this.

newCall(executeFields.

request).

execute()}The class ExecuteFields could probably be named better, but the idea of it is simple.

It just encapsulates the required parameters we will need to make a request.

Since the newCall function only requires one parameter, the request, that will be the only field we need to add.

The odd part of this extension function is the init parameter that it takes.

This init parameter is a function type with receiver, this means the function we are passing into execute operates on an instance of ExecuteFields.

The apply function takes exactly that signature, so we can see in the following example how it works:The apply function lets you operate on the caller inside the lambda as this .

I’ll often see this pattern used to set additional parameters that aren’t in the constructor at the site of initialization.

This is the perfect use case for what we’re trying to accomplish.

Now that we have the extension function for executing a call we can write code that looks like this:val response = client.

execute { request = Request.

Builder() .

url("https://www.

google.

com") .

method("GET", null) .

build()}It’s a good start, but not exactly what we’re going for.

We need to add some sugar to how we create requests.

Spicing up Request.

BuilderIn order to create a Request object we need to use a builder pattern.

Builder pattern is a totally valid design pattern, but can feel a bit clunky once you’ve started writing more and more Kotlin.

We can use the tricks above to sweeten our developer experience.

fun get(init: Request.

Builder.

() -> Unit): Request { return Request.

Builder().

get().

apply(init).

build()}val response = client.

execute { request = get { url("https://www.

google.

com") }}This is close to what we want, but still a half measure.

This allows us the nice function name get , but the fact that the Builder pattern expects method calls to set parameters kills the vibe.

We can fix that up with a small class that holds onto some parameters for us.

class RequestFields { lateinit var url: String internal var headers: MutableMap<String, String> = mutableMapOf() internal var body: RequestBodyFields?.= null}Now we can change up our original function to look like this:fun get(init: RequestFields.

() -> Unit): Request { val requestFields = RequestFields().

apply(init) val requestBuilder = Request.

Builder().

get() .

headers(Headers.

of(requestFields.

headers)) .

url(requestFields.

url) return requestBuilder.

build()}It is a bit heavier, but it holds all of the builder functionality into one spot.

It hides the implementation from our calling spot and allows us to write code like this:val response = client.

execute { request = get { url = "https://www.

google.

com" }}Boom!.That’s the exact code I was looking for from the beginning of this article!Since everything is sort of repeatable, I’m going to show you how the rest of the code is possible without too much explanation.

If there is anything that looks wrong, point it out to me in the comments.

The rest of the article is going to be code from here on out, thanks for sticking with me and I’ll see you next time.

(After getting everything to work I decided to Google if someone had already written something like this.

It looks like there is a Github repo here https://github.

com/rybalkinsd/kohttp with a similar approach.

We chose to do a few things differently, and the repo is worth checking out.

I just wanted to let everyone know I wasn’t ripping anyone off with this implementation.

).. More details

Leave a Reply