Mastering Kotlin DSL In Android - Step By Step Guide
In this blog, we are going to learn how to write Kotlin DSL in your Android project.
This blog is going to be a long one, so take some time out and then let's write your DSL together. We are going to cover the following topic,
- What is DSL in plain English?
- Are you using any DSL?
- Why we use DSL?
- How can we write our own DSL
- Basic Example explanations.
So let's begin.
What is DSL?
A Wikipedia definition says,
A domain - specific language (DSL) is a computer language specialized to a particular application domain . This is in contrast to a general-purpose language (GPL), which is broadly applicable across domains .
Like seriously?
In general human terms, DSL provides you a flexible tool of any specific language to leverage the power provided by the specific programming language.
Are you using any DSL?
If you are an android developer and are using kotlin in your project you might by chance knowingly or unknowingly be using DSL in your project? Can you think of an example?
Let me help you, Have you written any code like,
yourlist.forEach {
//your code is here
}
The above example is of using forEach in your list in Kotlin. A forEach is kotlin is an example of DSL
Why we use DSL?
We should use DSL to simplify and improve the complexity of the app and make the code more readable.
How can we write our own DSL?
Before jumping into writing our own DSL we need to learn about lambda with receiver.
I highly recommend going through the blog on Lambda with Receivers
but let me also you a give a brief overview of a lambda with receiver
Let's say we have a function buildString,
fun buildString(action: (StringBuilder).() -> Unit): String {
val stringBuilder = StringBuilder()
action(stringBuilder)
return stringBuilder.toString()
}
Here, we have a function buildString that takes action (it is a function) as a parameter. action function here takes StringBuilder as a parameter and the function buildString has String as return type.
Now, to use the buildString we write,
buildString {
append("<")
append("MindOrks")
append(">")
}
We use Kotlin's property here to pass the property related to StringBuilder, as we use the power of extension functions in buildString function.
We can also create custom DSL using,
Infix
In Kotlin, infix helps us to create custom DSL similar to like how we write in English. For example,
In English, we say "1 plus 2" to get the sum or to get the difference we say "1 minus 2". Similar things can be achieved in Kotin using infix calls.
To create an infix for adding to numbers we use,
infix fun Int.plus(number: Int) = this + number
Here, we created an extension function of Int, plus where it takes a number and returns the sum of this + number. Here this is the number the function is applied to.
So, to use this infix function we use,
val output = 1 plus 2
and here in the above code, we use the infix function which we created the plus to generate the output. When we print this in Logcat it will print 3 as the sum.
This is because we created an infix extension function to use plus text in place " + ", which we use as a traditional way.
Similarly, if we want to make infix function minus, we use
infix fun Int.minus(number: Int) = this - number
and to use it we use,
val output = 1 minus 2
Infix makes the code readable and very sorted for any human to read. So if anyone who has no idea about programming can also say that this is doing an addition or subtraction of two numbers.
Invoke
In invoke, the operator allows any object to be called as a function. Here, let's create a class called Student ,
class Student {
operator fun invoke(student: Student.() -> Unit) = student
fun addName(name: String) {
//implementation
}
fun addMarks(marks: Int) {
//implementation
}
}
In the class, we created a function invoke which takes a parameter of type Student and return student itself. So, here we can use all the methods of Student class in the object of the student itself.
Now, to use the Class we will still create the object like we do generally in Kotlin,
val student = Student()
and now as you can see we have another function called addName and addMarks which we will use it as DSL. But, here we can use it in two different ways. Both are as follows,
- Type - 1 ( Traditional Way)
student.addName("MindOrks")
student.addMarks(100)
the above code is like the old traditional way we used to do in Android. 2. DSL way
student {
addName("MindOrks")
addMarks(100)
}
Here, we use it the Kotlin DSL. If you noticed, we have the lambda like,
Student.() -> Unit
And if you noticed, we have a
.()
there. It specifies that the lambda has a receiver and to use it we need to create a construct of the class. This is how you can use invoke to create DSL.
You can get a detailed insight on how to work with lambda with a receiver here. Please read this to move ahead.
By the above ways, you can create your won DSLs in Kotlin.
Now, let's discuss the use-cases and examples of DSL in Android.
- Data Classes.
In this, let's discuss how we can create a DSL of Data Classes. Consider we have a data class Student
data class Student(
var name: String? = null,
var age: Int? = null,
var marks: Int? = null
)
In this, if we use the data class we write,
val student = Student(
"MindOrks",
20,
30
)
Now to convert the above code in DSL,
Now we will create a new lambda like the following,
fun student(student: Student.() -> Unit): Student = Student().apply(student)
Here in the above code,
we have a receiver, of parameter student and to use it as DSL we use,
val student = student {
name = "MindOrks"
age = 20
marks = 30
}
This is how we can covert a data class as DSL.
2. UI Elements
For this, let's consider textView as the UI Element. In that, we use like following,
textView.text = "MindOrks"
textView.setOnClickListener {
}
textView.setTextColor(Color.BLACK)
But to use it via DSL we use,
textView.apply {
text = "MindOrks"
setOnClickListener {
}
textColor(Color.BLACK)
}
We use apply to create DSL in any UI Element
3. JSON
to create a JSON in Android we use,
val jsonObject = JSONObject()
jsonObject.put("name","MindOrks")
jsonObject.put("age",20)
This is the traditional way to create JSON objects.
Now, let's see how we can create a DSL to create JSON. First, we will create a class and extend it with JSONObject(),
class Json() : JSONObject() {
}
and in this now, we will a constructor with lambda with receiver
constructor(json: Json.() -> Unit) : this() {
this.init()
}
the will use the infix to create a generic to add values to JSON objects.
It looks like,
infix fun <T> String.to(value: T) {
put(this, value)
}
here, it will put values with the string key and value will the type with which " to" is used.
Now, the complete class file looks like,
class Json() : JSONObject() {
constructor(json: Json.() -> Unit) : this() {
this.json()
}
infix fun <T> String.to(value: T) {
put(this, value)
}
}
To use this in the Activity file, we create JSON using the DSL we just created,
val json = Json {
"name" to "MindOrks"
"age" to 20
}
here,
- to is the infix which we created to put values to the JSON object
- JSON is the class that we created which takes key and value to create JSON Object.
When we print this in Logcat we get the following as output,
{"name":"MindOrks","age":20}
This is how you can create DSLs in your kotlin code.
Happy learning
Team MindOrks :)