ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Auto-generate Kotlin DSL

Credits: Icons made by Maxim Basinski and Smashicons from www.flaticon.com are licensed by CC 3.0 BY.

Introduction

DSL (Domain Specific Language) is a pretty common way to provide an abstraction of an specific application domain. For example Kotlin, Java and Swift are “general-purpose languages”. SQL and HTML are “domain specific languages”.

Nowadays, Kotlin is getting more and more popular in the DSL world for the features it provides to create your own DSL:

I’m not going to write much about how to use Kotlin to create your own DSL as there are plenty of great posts about this (also this and this talks from the KotlinConf 2018) but I would like to focus more in other aspects.

I faced some issues after trying to learn and develop my own DSL with Kotlin and I think maybe you will end up in the same situation. So I wanted to share some thoughts around this and a possible solution to alleviate it.

  1. Scary syntax?
  2. Immutability
  3. Boilerplate
  4. ✅ Tackle all at once

1. Scary syntax?

At first sight, the Kotlin syntax for creating a DSL could look a little scary. In the following code block we have one of the simplest DSL that you can create:

fun person(block: Person.() -> Unit): Person = Person().apply(block)class Person(var name: String? = null, var age: Int? = null)

This could be a little intimidating as we have plenty of concepts in just 2 lines of codes and this is just to being able to create a new Person instance in this way:

val me = person {
name
= "Juan"
age
= 34
}

Just to give you some details on what it’s going on here:

  • Higher-order functions to receive a lambda as parameter.
  • Person.() -> Unit: Lambda with Receiver to be able to set later the name and age. () means the function will not receive parameters and Unit is the return type.
  • apply extension function + block function being executed inside Person new instance.
  • var name: String? = null: Default and nullable values.
  • If last parameter is a function, then it can be defined outside parentheses

If you didn’t hear about one of these concepts then you can pick the links that I posted at the beginning of this post with different language features.

2. Immutability

I’m sure that you noticed that our Person class has default and nullable parameters in the constructor. It means that you can create an instance of a person like this Person() and name/age are going to be null. Well, this doesn’t look so good.

One approach to avoid this is to create a Builder:

fun person(block: PersonBuilder.() -> Unit): Person = PersonBuilder().apply(block).build()

class PersonBuilder() {
var name: String by Delegates.notNull()

var age: Int by Delegates.notNull()

fun build(): Person = Person(name, age)
}

This is much better! but now we have another class to maintain, so if we change Person parameters then we need to update the Builder.

3. Boilerplate

So every time we want to provide a DSL like the one we created for Person class we need to create again another Builder, provide a function with all the fancy stuff that we reviewed and so on.

These are just some of the repeated tasks that brings creating your own DSL. After fighting with these and other tasks to maintain my DSLs, I decided to create something that helps me with all this process.

4. Tackle all at once

At the end, I found myself repeating these tasks again and again, and what is better to perform repeated task than a piece of software :) so here you have a library to help you creating DSLs.

AutoDsl

AutoDsl is a library that auto-generates Kotlin DSL using annotation processing.

This library will help you to create expressive, immutable and type-safe DSL without boilerplate.

In order to use it just add the repository and dependencies:

repositories {
maven { url "https://dl.bintray.com/juanchosaravia/autodsl" }
}
dependencies {
api "com.juanchosaravia.autodsl:annotation:latest_version"
kapt "com.juanchosaravia.autodsl:processor:latest_version"
}

Note: Latest version is in the github repo.

Now let’s take the previous example and create the Person DSL. Just define your class with the correct parameters (immutable) as you normally would do and add the @AutoDsl annotation to the class like this:

@AutoDsl
class Person(val name: String, val age: Int)

And that’s it!

AutoDsl will generate the Builder and the person function for you:

person {
name
= "Juan"
age
= 34
}

AutoDsl Collections

Collections are one of the most exiting parts as AutoDsl will take care of that for you and provide some useful functions. Let’s add some friends to our Person class:

@AutoDsl
class Person(val name: String,
val age: Int,
val friends: List<Person>?)

The created DSL allows you to add new friends like this:

person {
name = "Juan"
age = 34
friends {
+person
{
name = "Arturo"
age = 28
}
+person
{
name = "Tiwa"
age = 30
}
}
}

Out-of-the-box AutoDsl will use the Kotlin feature “Operator overloading” to add friends with the + keyword and then the Person DSL.

More features

There are more features that I invite you to review in more detail in the Wiki Page: https://github.com/juanchosaravia/autodsl/wiki

But here you have a brief summary of them:

  • Custom DSL name: You can change the default DSL name: @AutoDsl(dslName = "newPerson")
  • Required and Optional parameters: Required parameters will be enforced by throwing an exception in case are not set. Optional ones are available by just adding ? in the parameter definition.
  • Automatic DslMarker: Will provide DSL Marker context automatically for your annotated classes.
  • Java Friendly: The generated Builder will have methods to be used from Java as a regular builder.
  • AutoDslConstructor: Annotation to indicate which constructor use when you have more than one and it’s not the first one.
  • AutoDslCollection: Annotation to give you more control over collections like specifying the concrete collection type or indicate the collection will not be nested but inlined.

AutoDsl is open source with Apache License v2.0 so feel free to contribute and report any bug in the github issue tracker.

I wanted to give a special thanks to Arturo Ayala 👏 🚀 who helps me in the development of the library. You can find him also in twitter.

If you liked this post please don’t forget to 👏 I’ll really appreciate it!

Twitter: https://twitter.com/juanchosaravia

Linkedin: https://www.linkedin.com/in/juansaravia

Cheers!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Juan Ignacio Saravia

Android Dev @ Globant | Argentinian living in the US