At Over, we’ve been using Kotlin since we started the new Android version of the app in November 2018. From day one, we’ve been excited to try the language features and leverage them to build our mobile design app.
One of the features we were most excited about was extension functions.
What is an Extension Function?
An extension function allows you to extend a classes functionality without needing to edit (or have access to) the class itself. They are great for libraries whose classes you can’t access.
This is an example of an extension function in Kotlin:
fun Canvas.center(): Point = Point(width / 2f, height / 2f)
You can then use it wherever required, as if that function formed part of the Canvas
class itself, although it is not explicitly part of the API:
val centerPoint = canvas.center()
How we overused them:
When we first started writing Kotlin, we were eager to use the new functionality of the language. We wanted to use extension functions and we didn’t realise the consequences of over-using them. We ended up with a code base that used them everywhere.
The main functionality of the app (i.e. the rendering of projects), was written with extension functions. Want to render an image to the screen? Extension function. Want to render some text to the screen? Extension function. Want to do anything that doesn’t quite fit into the MVVM/MVP/MVI model? Extension function.
This got tricky when we needed to start doing background loading and caching. Where should that logic and state be kept if everything is just an extension function? 🤨
It was at this point that we realised we had overused extension functions. Instead of creating new classes and concepts, we had created extension functions.
This meant we needed to do a large refactor of the rendering to remove extension functions and rather create a new class to perform this related functionality.
Disclaimer
This is not a blog post stating that “extension functions are terrible! don’t use them!”. They are an extremely useful feature of the language and we still use them at Over. We are just a bit more careful about what we use them for and where.
We don’t do this ❌
We don’t use extension functions for non-related methods. For instance, this example below should not form part of the String
API. It is unrelated functionality to the String
class and shouldn’t be provided as an extension on that class since its specific to our business logic.
fun String.toUserProperties() : UserProperties {
return UserProperties(this.toUppercase())
}
In this case, we would rather not make the extension function at all and just use the constructor of the UserProperties
class to get this functionality.
One thing to remember is that this function will be made available to your entire project (if you don’t make it private), which is also a great reason not to make an extension function.
We do this ✅
We add extension functions onto classes when it makes sense to do so and the function relates to the class that we are extending.
For example, this method can totally be a part of the String
API since it is related to String
manipulation and it is commonly used throughout our app:
fun String.toGraphemeCharsList(): List<String> {
// do something to get list of grapheme characters
}
Side note: For those who are unfamiliar with the word grapheme
. A grapheme is the smallest unit of a writing system. In Java/Kotlin, calling the loved String.toCharArray()
method, will return characters in their unicode form. So if you are working with emoji or special characters, they are generally made up of more than one unicode character. For instance, the following emoji: 👩👩👧 is actually a combination of multiple emojis (as well as multiple unicode characters), so String.toCharArray()
returns the “incorrect” number of visible characters on screen, it returns the number of unicode characters. Read more about this here.
However, we favor creating new classes for new behaviour, instead of adding extension functions onto existing classes.
We use extension functions on classes that aren’t extendable (i.e. classes that are part of the framework or primitives). We also create private extensions and use them only in the file they were created.
Summary 📃
Extension functions are a powerful feature in Kotlin that can be overused if you are not careful about when and where you use them. We made that mistake early on and now we are a lot more aware of our usages of them.
Hopefully this article helps you reason about extension functions, when you are considering using them in your code.
Have any thoughts or questions? Find me on Twitter.