· 4 min read
Ninja Functions in Kotlin. Understanding the power of generics (KAD 12)
The combined use of several Kotlin features mixed with the use of generics allow to create functions that will greatly simplify your code, while maintaining its readability.
There are several functions included in the Kotlin library that are really useful, and once you have mastered some concepts they will be also very easy to use.
If you want to learn what Kotlin’s situation is in the market, why you should learn it and the first steps to see how easy it is, I have prepared a free hour and a half training that you can join by clicking here.
with function
Though there are several similar functions in the Kotlin standard library, I’m going to focus on with
to break it into pieces.
What does this function allow? With it, we can make blocks of code that use a variable as its context, so that we don’t need to repeat its name every time we use it.
They can replace the builders, without creating a specific one for each class.
For example, going back to the case of the ViewGroup
we had in the previous article, we could convert this code:
val childViews = (0..viewGroup.childCount - 1).map { viewGroup.getChildAt(it) }
into this:
with(viewGroup) {
val childViews = (0..childCount - 1).map { getChildAt(it) }
}
As you can see, the code inside the brackets behaves as if it were code inside the class itself.
How do we get this? We have already seen it before: with an extension function.
Extension functions as arguments for other functions
Things are getting more complicated, but this is so useful that you need to know about it.
You can define an extension function as a parameter for another function.
How would you implement the with
function to be able to execute the previous example? The simplest would be this:
inline fun with(view: ViewGroup, f: ViewGroup.() -> Unit) {
view.f()
}
The previous code receives a ViewGroup
as a parameter, and a lambda with receiver that applies to ViewGroup
. Nothing prevents the ViewGroup
from executing that function.
If you want to expand your knowledge about extension functions and lambda with receivers, I recommend you to give this video a go:
https://www.youtube.com/watch?v=dDJJXUi0IpQ
But this is too constrained. Are we going to need a similar function for each type of data?
Of course not.
Generic types
We can convert the above function to a generic one quite easily. Just replace ViewGroup
with T
:
inline fun with(obj: T, f: T.() -> Unit) {
obj.f()
}
Now it can be used by any type. An example:
with(textView) {
text = "Hello World"
visibility = View.VISIBLE
textSize = sp(14).toFloat()
}
But here we are missing out on an important power we were talking about at the beginning: that of acting as a builder.
Return a value of the Generic type
If we want it to act as a real builder, we need the constructed value to be returned somehow:
inline fun with(obj: T, f: T.() -> Unit): T {
obj.f()
return obj
}
That way, our code would look like this:
val textView = with(TextView(this)) {
text = "Hello World"
visibility = View.VISIBLE
textSize = sp(14).toFloat()
}
sp()
is a function from Anko library, which we have talked about before in this series of articles.
If you look at the official definition of the function, it is very similar to what we have done:
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
The main difference is that the extension function returns a value that may be different from the one being passed as an argument.
To achieve the same result using the regular with
function, we would need to do the following:
val textView = with(TextView(this)) {
text = "Hello World"
visibility = View.VISIBLE
textSize = sp(14).toFloat()
this
}
The last line implies that the object that is executing the extension function will be returned.
Other interesting functions
There is a function that works very similar to what we wanted to get in the previous section, and that is called apply
.
apply
Instead of passing the object by parameter, this function acts as an extension function for that object:
val textView = TextView(this).apply {
text = "Hello World"
visibility = View.VISIBLE
textSize = sp(14).toFloat()
}
let
If the corresponding object is not null, it will execute the code inside the function:
textView?.text?.let { toast(it) }
The text will be displayed in a toast only if both the TextView
and the text are not null.
Conclusion
Using the power of generic types combined with extension functions, we can do very interesting things.
I encourage you to create your own functions that allow you to make your daily work easier.
And if you want to learn how to use Kotlin to develop your own Android Apps, I recommend you take a look at my free training.