Kotlin DSL: Builder pattern by delegation pt. 2

Let's create a fluent and painless DSL

Davide Giuseppe Farella
4 min readMay 3, 2019

In the first part of this serie we've seen when builder pattern could be useful and how delegation could help us to improve the readability of our code.

Now it's time to implement our delegation strategy.

Let's procede step by step:

1. Avoid the property initialization and nullability

This is pretty straightforward: using an implementation of ReadWriteProperty we can avoid to initialize someValue and also a avoid the null-check before call toLong()

Note: in this case, someValue is not contained in any class, so we need to use Any? as first generic of ReadWriteProperty since thisRef is instance of Nothing

An implementation like this is pretty dangerous since someValue is marked as Int, but what's gonna happen if we try to accesso it before setting a value?

We'll have a NPE of course!!

But… We're designing a DSL, we want to have an Exception if that required value is null!

Well, but perhaps a NPE won't describe the issue that good. We could do better!

2. Have a specific exception if a required values has not been set properly

Here we defined a custom Exception to throw in case our value has not been set.

If someValue has not been set ( so the field in ReadWriteProperty is still null ), we will receive a good describing description: Required property 'someValue' is null

Extra: for brevity purpose here we're only showing the name of the property which has not been set, but in a real case scenario we could also pass the container class ( thisRef?.let { it::class } ) for better identify the fall.

This is already pretty good, but we can do better!

3. Distinction between Optional and Required values

Here we extracted an abstract BaseProperty class from our old delegation() function, since we need to share some logic between optional() and required(), we will just use a different logic when accessing the value.

In particular:

  • for required() we keep checking if field is null and so throw RequiredPropertyNotSetException
  • while for optional() we simply return value casted as T ( since for some weird reason, for the compiler T? is different from T: Any? 🙄 — please comment below if that reason is not that weird, never researched for an explanation )

As you can simply wonder, required() accepts T: Any as parameter, while optional() accepts T: Any?

Note: as I described in the comment, the Kotlin's Playground shows that error on println( someOptionalValue ) if think is kinda weird and don't know if it's just an error about Kotlin's Playground or not, any way that won't be a problem, since we are using an optional value, we would null-check it anyway.

4. Use a backing resource

Well, until here it has been pretty easy, we just needed to be a little creative for this solution; now it's time to play hard, so let's try to define some small concise steps.

a. Provide Resources

First we need a context that provides the needed Resources so, in our Android scenario, the Resources class.

For our scope, since we are creating a Builder, we could create a sample interface called ResourcedBuilder that provides Resources

Simple as that.

b. Implementing the Resources provider

Now we need to implement that interface, but hey, we're playing with delegation right? So first we're gonna create a ReadOnlyProperty of type Resources.

What's that for? You will see soon 🙂

We just declared an operator function for create a ReadOnlyProperty of Resource by the operator invoke() on a receiver of type Context or Resources ( i.e. context() or resources() )

Looks pretty magic right? Well, that's gonna be even more magic

Wait…What? What the hell just happened? Where's the val resources: Resources implementation? 🤯

Let me explain you: we're extending ResourcedBuilder which has a val resources: Resources, right? So we are delegating the Context ( or Resources ) to implement that, by the invoke operator, which creates a ResourcesProperty, which also has a concrete val resources: Resources.

So, basically, by calling someMagicBuilder.resources, the ResourcesProperty created by the Context/Resources will provide Resources for us 🤗

c. Delegate ReadWriteProperty to resolve Resources for us

Let's start with the idea that we have to "manually" declare the right way for resolve the Resources, based on the type of our variable ( e.g. if variable is a Drawable we need to call resources.getDrawable( resId ) ); so it's a great idea to handle that with a custom Exception

In this way we can clearly find out when we are trying to a retrieve a Resource of a type that is not implemented.

This is a basic function for resolve some types of resource from a given Class<T> and a resourceId, it will be used from our ReadWriteProperty; let's find how!

Here we changed our BaseProperty is some ways:

  • First we changed the first generic of ReadWriteProperty from Any to ResourcedBuilder, so we can ensure access to the Resources
  • Then we added 2 parameters to the constructor: one is the KClass of our T ( we need it since we can't resolve it from a generic that is not inlined — reified ), the second one is a lambda that returns the Int id for the backing resource ( it is a lambda since we value of the Resource id can be changed at any time in our Builder ).
  • Then we declared field as private so it won't be accessible to the children classes, and so we are sure do not pick the wrong value
  • Lastly we declared getField for retrieve the right value, which is the field in not null, else try to resolve the Resource from ResourcesBulder

Now, finally we wrapped up everything and we are ready to achieve the syntax promised in the first part of this serie!

Conclusion

Sadly the last Kotlin's Playground's are not executable since Android environment is required. But I hope I've been pretty clear and gave you some crazy ideas 🙂

Please let me hear your feedback down here, clap if you liked or check all the ways to find me into my brand new website ( yep, sorry, I'm pretty proud of my first decent work in Js / ReactJs 😜 )

--

--