Domain-Specific Languages In Kotlin: Getting Started

In this Kotlin tutorial, learn how to create a DSL using Kotlin lambdas with receivers, builder pattern and extension functions! By Tino Balint.

Leave a rating/review
Download materials
Save for later
Share

In modern programming, developers strive to write clean and readable code that is intuitive and easy to use. Normally, to achieve this, developers use design patterns and create specific architectural solutions. Code written in such a way is maintainable and readable by experienced developers, but what if you could write code that everybody can understand and reason about?

Kotlin gives you the tools to help craft code into something which feels more natural to use, through a domain-specific language — or DSL! :]

In this tutorial, you’ll create DSLs to show Android dialogs, in different DSL styles, and data models you need to use for the application. Your app will show a list of puppies, which you can attempt to favorite, using the mentioned dialogs. Throughout implementing these features, you’ll:

  • Learn the fundamentals of building a DSL in Kotlin.
  • Learn the basics of lambdas with and without receivers.
  • Create a DSL using the builder pattern.
  • Create a DSL using Kotlin extension functions.

Before building a DSL of your own, it’s best to take a look at some of DSLs you already use, but may not be aware of.

Domain-Specific Languages

Domain-specific languages, or DSLs, are languages specialized for a specific part of an app. It is used to extract a part of the code to make it reusable and more understandable. As opposed to a function or a method, DSLs also change the way you use and write code. Usually, DSLs make the code more readable, almost like a spoken language. This means that even people who don’t understand the architecture behind the code will be able to grasp the meaning of it.

To make a DSL means to change the syntax of that specific part of the code. In Kotlin, this is achieved by using lambda and extension functions, and expressions, to remove a lot of boilerplate code and hide the internal implementation from the user. One of the best examples is a crucial part of Android development – Gradle.

Popular DSLs

If you’re an Android developer, chances are that you’ve already seen a DSL before. However, would you be surprised to learn that you write the Gradle build files using a DSL? Take a look at the following:

android {
  compileSdkVersion 28
  defaultConfig {
    applicationId "android.raywenderlich.com.puppyparadise"
    minSdkVersion 19
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  }
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
  }
}

Look at the way the code is written in any build.gradle file. Objects use curly brackets to specify their contents. Inside those brackets, you set the value of certain parameters by writing a parameter name and then a value.

It may look like this file simply contains notes or diagram documentation for some class, but in reality, it’s just a special DSL. Below, check out the same code written without the DSL:

Android(28,
  DefaultConfig("android.raywenderlich.com.puppyparadise",
    19,
    28,
    1,
    "1.0",
    "android.support.test.runner.AndroidJUnitRunner"
  )
),
  BuildTypes(
  Release(false,
    getDefaultProguardFile('proguard-android-optimize.txt'),
    'proguard-rules.pro'
    )
)

At first glance, it may look unclear, mostly because each object you build has many parameters, and you don’t know their names. By comparing these two approaches, you can see that the version which uses a DSL is easier to read. But more importantly, because you know the parameters and properties you can access, it’s easier to maintain, as well. This is one of the requirements this DSL has, and because of it, it’s much easier to use than the default – using objects.

Another popular example in Android is called Anko. It’s a group of Kotlin DSL libraries which cover a lot of functionality. The libraries not only contain DSLs for UI components, but for handling networking calls, background tasks, and database management. The code snippet below uses Anko to execute a background call using Kotlin Coroutines:

suspend fun getData(): Data { ... }

class MyActivity : Activity() {
  fun loadAndShowData() {
    // Ref<T> uses the WeakReference under the hood
    val ref: Ref<MyActivity> = this.asReference()
	
    async(UI) {
      val data = getData()			

      // Use ref() instead of this@MyActivity
      ref().showData(data)
    }
  }

  fun showData(data: Data) { ... }
}

The curly brackets when calling asnyc is one of the signatures of DSLs. And here, you state that a function call will be done asynchronously, without really delving into the implementation details. Just like it should be. If you want to see more examples of Anko library, check out the official GitHub page.

Getting Started

Before you begin writing DSLs, download the tutorial materials from the Download Materials button at the top or bottom of the page. You’ll see the starter and final projects, which you’ll use throughout the tutorial.

After the project is set up, build and run the app and you’ll get a screen like this:

You can see a scrollable screen with cute puppies. You can click on any of them to open the dialog.

After clicking on a puppy, a dialog is shown with a blurred background. You can choose if you like the puppy or not. If you select YES option, a heart icon is shown in the bottom-right corner. Selecting NO option removes the heart, but you won’t need to use it because all the puppies are too cute to not be liked. :]

This dialog is the main focus of the tutorial. Open the class DialogPopupView. If you look at the code, you can see that the class contains several properties of the dialog. Those include click listeners and the methods to blur the background. At the bottom, you can find the DialogPopupBuilder which is responsible for building and setting those properties, ultimately creating a dialog.

Open PuppyActivity.kt and look inside the createDialogPopup():

DialogPopupView.builder(this)
    .viewToBlur(rootView)
    .titleText(titleText)
    .negativeText(negativeText)
    .positiveText(positiveText)
    .onBackgroundClickAction(backgroundClickAction)
    .onCancelClickAction(cancelActionClick)
    .onPositiveClickAction(positiveClickAction)
    .build()

This method creates a DialogPopupView using the builder pattern. The goal is to change this call to look more user-friendly, using a DSL. Roughly speaking, you’re going to clean up the code above, so it can be written as follows:

buildDialog(this) {
  viewToBlur(rootView)
  title(titleText)
  positiveAction(positiveText) { positiveClickAction() }
  negativeAction(negativeText) { negativeClickAction() }
  backgroundAction { backgroundClickAction() }
}

Note: If you’re unfamiliar with the builder pattern, you should check out our Intermediate Design Patterns in Swift. Although it’s a tutorial written for Swift, the concepts described in that tutorial translate to all programming languages including Kotlin.

As you can see, with a DSL, there will be less code and no dots required to connect the chained calls, for the builder pattern.