6 magic sugars that can make your Kotlin codebase happier — Part 3

Don’t cry because it’s over, smile because it happened.
Dr. Seuss

Unfortunately, all journeys have to end😿. This is the last — but not least — part of my three-part series about the sweetest Kotlin candies. In previous posts you had a chance to get familiar with:

  • sealed classes to encapsulate different state definitions in different cases.
  • when() functions to permute in a clean and elegant way.
  • with() functions to omit qualifiers.
  • inline functions and reified types to write your own withCorrectType() functions with.

This time I have something really special, and you can treat it as the frosting on the cake. First, I would like to introduce you to code association and how to use the delegation mechanism of the Kotlin language to make it simple and short. Then, in the second part of the article, you will become more familiar with writing your own DSL (Domain-Specific Language) structures.

Pour a cup of your favourite beverage and enjoy reading.

KProperty: make your composition great again

It is said that the wrong abstraction is worse than no abstraction. Actually, what this points out is that inheritance is a really tricky mechanism and it should be used carefully. Some books say that you should use composition over inheritance and that it is key to good design.

Kotlin has a built-in delegation pattern, and it is super easy to use it for aggregation.

As you can see, there is no magic here. The Component class implements Navigable and Searchable interfaces and then, using the language keyword by, applies behaviours from navigable and searchable constructor parameters. This language construct is super useful as it cuts tons of boilerplate code, but it is not a silver bullet for all association scenarios.

Imagine that you need to mark your interfaces as internal because you want to make them a part of your internal architecture:

How does it affect the Component class? Sadly, you broke the code and now you are getting a compilation error💔. This is because the Kotlin compiler does not permit exposure of the internal components of your module.

So, if there is no need to expose Navigable and Searchable dependencies, you can:

  • Remove the constructor.
  • Use composition instead of aggregation.
  • Define the ComponentInterface that contains method signatures from Navigable and Searchable interfaces.

Wow! That escalated quickly. From zero boilerplate aggregation you went to nasty-looking composition. But don’t cry, because Kotlin always has some sugar to make your codebase happier. Kotlin not only supports method delegation to the specified object using the by keyword, but also has a mechanism to delegate properties. You have probably been using it by applying the lazy() delegate to properties that should be initialised in access time.

Being lazy is good, but how is it connected with your case? It means you can apply the same pattern that is used to define the lazy() function to re-factor your composition code.

Warning! This code can change your life drastically💣.

ReferencedProperty is a class that takes two functions as parameters in the constructor and defines two operators.

  • The get function takes nothing and returns a generic type T. It is an accessor function.
  • The set function takes a generic type T and returns Unit. It is a mutator function. Additionally, set has a default value set to empty function.
  • The fun getValue() operator invokes the get() function.
  • The fun setValue() operator invokes the set() function with a value parameter.

The most important thing to know is that operators are used by the property delegation mechanism. After the ReferencedProperty class declaration, you can find two generic ref() methods that return an instance of the ReferencedProperty type. Why two? The first one will be used for properties marked as var and the second one for those marked as val.

Let’s use ref() functions to clean-up your composition.

It looks much cleaner right now, doesn’t it? The ref() function allows you to delegate the property definition to another component. By using :: you can get KProperty0 for val and KMutableProperty0 for var. These are representations of your properties and you can easily access getter and setter functions from them. It may sound complicated but I really encourage you to play with this syntax. Hopefully, you will find it useful as it can save a lot of lines of your codebase. It saved mine! There is one caveat that you need to be aware of. This syntax uses reflection under the hood so make sure you are ready to take such trade-off.

It is about time to dive into the waters of DSL. Get ready.

Write DSLs to cover up ugliness

One of the coolest features of Kotlin is that it gives you the ability to write clear DSLs. A Domain-Specific Language is a computer language specialized to a particular application domain. The perfect example is a well known Anko library that was made to build the user interface of Android application programmatically. Additionally, you can use it to write statements against the SQLite database and schedule background tasks (Coroutines under the hood😎).

Not only does it simplify the code, it also makes it more readable.

You might think that building such a pleasant API needs advanced Kotlin knowledge, but you couldn’t be more wrong! Recently, I decided to build an additional layer on the top of my acceptance tests. My main goal was to separate the WHAT from the HOW. What does that mean?

  • The WHAT should be the test itself that contains only the information that is to be tested.
  • The HOW describes the internals of the framework that is used for testing.

In addition, I wanted my test cases to look the same for Android written in Kotlin and for iOS written in Swift. Writing DSLs for those were the perfect solution.

Let’s have a look at the syntax I wanted to achieve.

This is a really nice and clean looking piece of code, isn’t it? The setup() function contains prerequisites for the main test case. It makes an app to open ScreenOne using a navigation controller. Can you tell how this DSL is implemented under the hood? Of course, I am not asking about the framework that sends the commands to the target device. If you have no idea, let me explain it to you.

As you can see, the implementation is trivial. The navigation() function takes a block as a parameter that is nothing else but a function literal with a receiver. The body of the function invokes the block() directly on the NavController object. NavController is just an object that defines the behaviour function. This is where you implement the HOW, using any of your favourite testing frameworks.

There is one more construct living in the test case that is worth explaining.

What is so interesting in the DSL mentioned above? It is almost the same as the navigation { openScreenOne() } except that after the closing bracket, there is another chained construct. How is it possible to chain such constructions? Is it a function invocation or something totally different? To make it more clear, I will change this DSL to something more obvious.

The main difference is that before invoking the openSearch() function (the code shows the function using parentheses) you can see the dot notation. This shows in a very explicit way that openSearch() is a function called on some object that is returned from toolbar {}. Now it should be an easy task to implement such DSL and the implementation can look like this:

The toolbar() function looks similar to the previously implemented navigation() one. It takes the function literal with a receiver as a parameter and additionally returns an instance of the Toolbar object. It allows you to invoke the openSearch() function, which contains the HOW and returns an instance of the SearchToolbar object. Again the SearchToolbar is an object that defines the behaviour function type() that contains the HOW. Awesome!

The last riddle is the dot notation and how to achieve the DSL syntax that omits it. To be honest, the implementation is based more on a hack, than an elegant dedicated solution.

Marking the openSearch() function as infix allows you to call it with infix notation. The infix keyword was added to the Kotlin language to answer a need for writing arithmetical and logical statements. It should not be abused to achieve other constructs. Additionally, infix can be used only for functions with one parameter. This “limitation” can cause inconsistencies in your DSL.

I hate writing endings as I feel that I am losing a friend―you. But, as I said at the beginning of this article:

All journeys have to end😿.

I hope that this three-part series has not ended but helped to start your own journey through the Kotlin language syntax. I wish you clean code and a happy codebase.

If this was interesting to you, please give some claps 👏 or let me know on Twitter.