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 andreified
types to write your ownwithCorrectType()
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 fromNavigable
andSearchable
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 typeT
. It is an accessor function. - The
set
function takes a generic typeT
and returnsUnit
. It is a mutator function. Additionally,set
has a default value set to empty function. - The
fun
getValue() operator invokes theget()
function. - The
fun
setValue() operator invokes theset()
function with avalue
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.