Kotlin DSL: Builder pattern by delegation pt. 1
Let's create a fluent and painless DSL
Introduction
I'm Davide Giuseppe Farella, a freelance Android developer from Italy and a Kotlin lover ❤️
This is my very first story on Medium, hope you will like it.
Builder pattern: why?
Builder pattern is a very common pattern for create a configurable abstraction layer on some APIs: it allow us ( or our user, in case of a library ) to define an infinite number of parameters, without declare an equal infinite number of methods; well, Kotlin came to help us with default values, but in some case that's not enough.
An example goal
I'm gonna illustrate you an example where create an internal DSL with a builder pattern could be useful, but first let's take a step back.
The core language of the Android SDK is Java. Well, that's already a good reason. Nobody likes Java, right? 🙂
The Android SDK offers thousands of APIs, some newer, some older. Some newer based on another older API. Some deprecated, some available only from a certain version of Android.
So many times we end up by creating a simple abstraction layer on them.
I did it many times… Then I copy-paste that code for re-use in another project. Then it grows up; I update it, improve it, expand it again; then I chose to extract it in a library, usually with a well designed DSL ( at least, that's my hope 🙂 )
Let's create a Notification
We want to inform our user that the cleaning of the cache has completed successfully.
That's a simple notification: without a body, an action, nothing… Just the bare minimum for create a Notification.
That's pretty verbose, do you agree?
Wouldn't you prefer something like this?
This code looks just a little more concise and clean, but image a real life scenario, with many other parameters. That would be a way better than the original API.
Builder pattern: how?
Let's take the most configurable block from the code above: the notification block.
It could have many parameters, some required, some optional, some parameters that exclude others ( like title: String
and titleRes: Int
)…
Here's a simple Builder:
Do you spot any problem here?
I spot many problems:
- both
title
andtitleRes
are nullable and one of them is required, so we need to make an unsafe call like!!
An exception need to be thrown in this case, but we would like something that explain the problem better than a NPE, right? So what do we do? Will we add many assertTitle()
, assertThis()
, assertThat()
? NO!
- We need to keep calling
context.getString()
,context.getThat()
, and so on…
Would you believe me if i tell you that you can:
- avoid to manually retrieve resources every time
- avoid to keep checking for nullable values
- have not-nullable values without initialize them
- have well-declarative exceptions without thow them or make assertions
Would you?
Would you believe me if I tell you that you can have a class like this, where title
and smallIcon
are not nullable while contextText
is?
As you can see, we only need to call title
, without manually check if it has been set, otherwise try to retrieve titleRes
, check this other one for assert it's not null, etc.
In this case title
is not-nullable: if it has not been set, the delegated property will check for its backing resource titleRes
and, if also this one has not been set, a custom exception will be thrown by the delegated property itself.
Also contentText
has a backing resource contentTextRes
, but in this case we used optional
, so it's nullable
I guess you also noticed that ResourcedBuilder by context()
: what's that? ResourcedBuilder
is a simple interface that exposes a Resources
value; this interface is anonymously implemented by the invoke function declared on Context
.
How did we get rid of all the context.getString()
?
Well, optional
and required
are implementations of ReadWriteProperty
that accepts ResourcedBuilder
as the type of object which owns the delegated property. So our delegated property will make the dirty work.
This is the power of delegation 😎
There are some lines of code behind that and for this reason I prefer to carefully illustrate and explain them in the second part, for avoid to annoy you with a 30min story.
Don't worry, I'm not that evil, here's a Repository on GitHub were you can take a look at the code
Hope you like it. Please feel free to leave your feedback down here 🙂 See you soon! 👻