The Ugly Truth about Extension Functions in Kotlin

Looking beneath the tip of the iceberg

Rahul Chowdhury
AndroidPub

--

Last week I talked about how to speed up your Android app development using Kotlin and it’s plethora of shortcuts. Among all the points was a section based on using something called Extension Functions to extend Android API features and save your future development time.

In the event that you haven’t gone through the other article or have forgotten about how extension functions can be helpful, let me run you through a quick intro of what extension functions really are in Kotlin.

Understanding Extension Functions

The underlying concept of extension functions is pretty straightforward. You pick up a receiver class which can be a class present in your project or the Android SDK or any third-party library, and dynamically inject a member function into the class which acts pretty much same as a regularly defined member function in the class.

This might sound a little out of the world, so let’s have a look at a simple example to clear all the mumbo jumbo.

Consider we have a class called Hero, defined like this,

class Hero {
fun useSuperpowers() {
println("Applied super powers")
}
}

Now let’s say we need to add some ability to our hero without actually mutating it, for whatsoever reason (the class might be a part of a library or we simply don’t want to mess up the original declaration). How do we do that?

The answer is simple. Write an extension function for the class. How? This is how.

fun Hero.savePlanet() {
useSuperpowers()
}

Just declare a function on the receiver class, which is Hero in our case and write out the implementation. This function can be defined anywhere in the project and can be referenced anywhere by calling the function from the class instance, which is,

val superman = Hero()
superman.savePlanet()

That’s the beauty of extensions functions. We are calling it like a regular function although it is not defined anywhere inside the class.

One thing if you haven’t noticed already, I am calling the function useSuperpowers()directly inside the extension function. This is made possible because you have access to all the members of the receiver class inside your function block. Therefore, call to the member function useSuperpowers() gets resolved without any issue.

Behind the scenes

I was pretty psyched out when I first read about extensions functions. They were like magic. However, the question was, how do they actually work? Or rather how is this sort of dark magic made possible?

As the official Kotlin documentation says,

Extensions are resolved statically

Well, that didn’t offer much explanation, at least to me. I went down deeper to find out what that actually means until I stumbled upon this thread reply by Andrey Breslav (Lead Language Designer of Kotlin).

As explained by Andrey himself, the extension functions are nothing but regular static functions which takes in an instance of the receiver class as a parameter implicitly when you define the function and operates on that. This is the reason why you have access to all the members of the class inside your function block. They do not have any connection the receiver classes in any other form.

As for the function itself and its body, they are taken in as a parameter as well (an example of a Higher Order Function) and executed when the call to the extension function is made.

Possible use cases

All these seem very fascinating but where’s the real application of extension functions, you may ask? Well, I have an answer to that as well.

One of the main use cases for extension functions is the extension of platform APIs, or simply put, creating better Utils classes for your project. How? Let’s see an example,

inline fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) {
val editor = edit()
editor.func()
editor.apply()
}

This extension function extends the capability of Android’s regular SharedPreferences by allowing you to save data easily like this,

sharedPreferences.edit {
putString("name", "Rahul")
putBoolean("isRegistered", true)
}

Cleaner and also eliminates the need to call editor.apply() everytime you need to save the data because it is handled internally inside the extension function, as defined by us.

You can have a whole library of extension functions like this and reuse across your projects to cut your development time by a significant margin.

Extension functions seem very elegant till now, so what’s the ugly part? That brings us to the next point.

Being Aware of the Limitations

Extension functions are powerful but to a certain limit. The design of extension function holds it back from being a hero in certain situations. We will be looking at two such cases where this splendid language feature fails to cast its charm.

Adding static functions to classes

Kotlin allows you to add static extension functions to classes only by means of a companion object. This means that if you want to add an extension function to a class which you want to access statically, you need to have your class definition in this fashion,

class Hero {
//Declaring an empty companion object here
companion object
fun useSuperpowers() {
println("Applied super powers")
}
}

Now our Hero class has a companion object in it which allows us access members defined inside the companion object in a statical manner which is Hero.functionName(). The trick that we have to apply now, is to add an extension function to this companion object and not the actual Hero class, like this,

fun Hero.Companion.sayHiToFans() {
println("Hi!")
}

Now, we can access this new function without an instance of the class, as you would call a regular static function.

Hero.sayHiToFans()

All these seem fine, so where’s the catch? Well, this methodology holds well when the receiving class has a companion object defined inside it, which is not the case when extending a platform SDK class and also not the case most of the times when extending other classes either present inside your project or in a third party library.

As always, let us go through an example to understand the situation better. This time we want to extend Android’s Uri class to add a static helper function fromRaw() which will form a Uri from a raw resource present inside our project and return it. This will be similar to Uri.fromFile() or Uri.fromParts(). Let’s declare the function.

fun Uri.fromRaw(packageName: String, rawFileName: String): Uri {
return parse("android.resource://$packageName/raw/$rawFileName")
}

Fine till now. You won’t be handed out with a compiler warning at this point. The problem occurs when you try to access it.

Uri.fromRaw("co.upcurve.hero", "heroEntry") //This function call is not possible

You will be getting a compile time error if you try to run your code now. This is because your extension function is only available by means of a companion object.

Why don’t we add our function to the companion object of Uri class? Actually, that’s not possible. Uri class doesn’t have a companion object in it because Android APIs were designed for Java and not for Kotlin so the following code won’t compile as well,

//Won't compile as there is no companion object in Uri class
fun Uri.Companion.fromRaw(packageName: String, rawFileName: String): Uri {
return parse("android.resource://$packageName/raw/$rawFileName")
}

How do we make use of the extension function then? We have to create a dummy Uri object and use that object to create the actual Uri object, like this,

val dummyVideoUri = Uri.Builder().build()
val realVideoUri = dummyVideoUri.fromRaw("co.upcurve.hero", "heroEntry")

This is actually the opposite of what extension functions are meant to do. We are bloating our code with dummy objects, extra code and creating a smelly project. Not the approach we would like to take.

Otherwise, we can just remove the receiver class from the function definition and refer it as it is without specifying any class name, like this,

val realVideoUri = fromRaw("co.upcurve.hero", "heroEntry")

That works pretty well, but does two things to your project,

  • clutters up your root namespace with a bunch of functions
  • in the event of having two different functions with a common signature but different implementation based on the class they are operating on, this approach fails as we can’t add two functions with the same signature to a common namespace

As of now, there is no support to add a static extension function to a Java or Kotlin class without the help of a companion object. There is also an open feature suggestion on Kotlin’s issue tracker but with acknowledgment from the JetBrains team till now. Maybe they will add it someday.

Member functions always win

In case we want to define an extension function which has the same signature as that of a member function present inside the receiver class, the member function is executed when we make a call to the function.

Given, we have our good old Hero class defined like this,

class Hero {
//Declaring an empty companion object here
companion object
fun useSuperpowers() {
println("Applied super powers")
}
}

If we try to add an extension function useSuperpowers() with a different implementation to our class, like this,

fun Hero.useSuperpowers() {
println("Out of energy")
}

Now, if we call our function we will get the output of the member function and not the newly added extension function. The scenario kind of looks like this,

superman.useSuperpowers() //Output: Applied super powers

As you can see clearly from the output that when we made the call, Kotlin chose the member function to execute and not the extension function.

But wait, isn’t it supposed to be the other way round? I mean, if I am extending something with some newly implementation then shouldn’t the extended capabilities be harnessed?

This is a potential source of bugs as we might add an extension function with the same signature of a member function, write some cool new stuff there and hope that the new implementation will get executed when we call the function. However, in reality, the old implementation(member function) will be used.

The good thing is that the compiler will notify you about this, so it’s a good practice to keep an eye on the lint warnings while developing to avoid potential bugs. In my opinion, though, the compiler should have thrown an error instead of showing a warning as the extension function defined here is useless.

Are they Worth Using?

Definitely yes. Extension functions might have their share of flaws but still, they are an excellent tool to enrich any existing API and make the development process easier, faster, cleaner — in short, pleasant.

Let’s hope that the wonderful team behind the language will recognize these issues and do something about them, but till now these minor hiccups are acceptable and should not be treated as a negative sign to using this wonderful capability.

This article was originally published at the Blog of Rahul Chowdhury on 4th of June, 2017.

--

--

Rahul Chowdhury
AndroidPub

I send a newsletter every Friday to help you become 1% better every single week at hulry.com/newsletter