Extension Functions and Properties in Kotlin
In this tutorial for Android, you’ll learn to use Kotlin extension functions and properties to extend the functionality of existing classes. By Rajdeep Singh.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Extension Functions and Properties in Kotlin
20 mins
Creating an Extension Manually
In this section, you’ll see what the extension functions decompile down to. You’ll also write a function to manually implement something similar, which will help you understand how it works.
Open Extensions.kt and select Tools ▸ Kotlin ▸ Show Kotlin Bytecode in the top menu:
Click Decompile and you’ll see the decompiled Java version for the Kotlin code you’ve written. Look at loadImage
:
public static final void loadImage(@NotNull ImageView $this$loadImage, @NotNull String imageUrl) {
Intrinsics.checkParameterIsNotNull($this$loadImage, "$this$loadImage");
Intrinsics.checkParameterIsNotNull(imageUrl, "imageUrl");
Glide.with((View)$this$loadImage).load(imageUrl).into($this$loadImage);
}
The Kotlin code for the loadImage
extension function you wrote looks like this:
fun ImageView.loadImage(imageUrl: String) {
Glide.with(this)
.load(imageUrl)
.into(this)
}
Look at the decompiled code and you’ll notice that it’s a static function that takes the receiver class of extension function as its first parameter. The remaining parameters are whatever you define.
Also, notice Intrinsics.checkParameterIsNotNull
, which throws an IllegalArgumentException
if the receiver is null.
Open LoginActivity and RegisterActivity and you’ll see that in the success case in both the login and registration flow, an Intent
opens the MainActivity with Intent.FLAG_ACTIVITY_NEW_TASK
and Intent.FLAG_ACTIVITY_CLEAR_TASK
flags to start the new activity.
You will first extract this functionality into a function which takes a Context
as parameter. Later you’ll replace this function with an extension function to make the code concise.
Open Extensions.kt and add the following snippet below TODO: 8
. Then click OK to add the required import statements:
fun startActivityAndClearStack(context: Context, clazz: Class<*>,
extras: Bundle?) {
val intent = Intent(context, clazz)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
if (extras != null) {
intent.putExtras(extras)
}
context.startActivity(intent)
}
Open LoginActivity and replace the code below TODO: 9
with the following snippet, then add the necessary imports:
startActivityAndClearStack(this, MainActivity::class.java, null)
Open RegisterActivity and replace the code below TODO: 10
with the following snippet and add the necessary imports:
startActivityAndClearStack(this, MainActivity::class.java, null)
Build and run; the app should work the same as before:
Now you will convert startActivityAndClearStack
to an extension function.
Open Extensions.kt and replace startActivityAndClearStack
with the following code snippet:
fun Context.startActivityAndClearStack(clazz: Class<*>, extras: Bundle?) {
val intent = Intent(this, clazz)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
if (extras != null) {
intent.putExtras(extras)
}
startActivity(intent)
}
Decompile Extensions.kt by selecting Tools ▸ Kotlin ▸ Show Kotlin Bytecode in the top menu and clicking Decompile button.
You’ll find the following decompiled snippet for the method you just added:
public static final void startActivityAndClearStack(
@NotNull Context $this$startActivityAndClearStack,
@NotNull Class clazz,
@Nullable Bundle extras) {
Intrinsics.checkParameterIsNotNull($this$startActivityAndClearStack, "$this$startActivityAndClearStack");
Intrinsics.checkParameterIsNotNull(clazz, "clazz");
Intent intent = new Intent($this$startActivityAndClearStack, clazz);
intent.setFlags(268468224);
if (extras != null) {
intent.putExtras(extras);
}
$this$startActivityAndClearStack.startActivity(intent);
}
This is very similar to the one you created earlier but with additional null checks and simplified flag value.
So now you know how to create the underlying code for extensions yourself.
Open LoginActivity and replace startActivityAndClearStack(this, MainActivity::class.java, null)
with the following snippet:
startActivityAndClearStack(MainActivity::class.java, null)
Open RegisterActivity and replace startActivityAndClearStack(this, MainActivity::class.java, null)
with the following snippet:
startActivityAndClearStack(MainActivity::class.java, null)
Build and run; the app should work the same as before:
Adding Username Suggester
In this section, you’ll add an extension function in EditText
to validate the username.
Usually, this is an API call to the server to determine whether or not the selected username is available and valid. But in this case, you’ll write a simple offline validator to ensure that each username ends with a number.
Open Extensions.kt and add the following import statements:
import android.widget.EditText
import java.util.regex.Pattern
Below TODO: 11
, add the following snippet:
fun EditText.validateUsername(): Boolean {
//1
val username = text.toString()
//2
val pattern = Pattern.compile("^[a-zA-Z]+[0-9]+$")
val matcher = pattern.matcher(username)
val isValid = matcher.matches()
//3
if (!isValid) {
error = context.getString(R.string.username_validation_error, username)
}
//4
return isValid
}
This code adds an extension function in EditText
. Going through the code, it:
- Takes the input from
EditText
and converts it into a string. - Declares a
regex
pattern that accept strings ending with some number, like bond007, coder1, etc. It then matches the input with theregex
and stores whether it’s a valid string or not. - Sets the error hint if the username is invalid.
- Returns whether the input entered is a valid username or not.
Open RegisterActivity and add the following snippet below TODO: 12
:
val isUsernameValid = binding.usernameInput.validateUsername()
if (!isUsernameValid) {
return
}
This code calls the validateUsername()
extension function and shows an error with a list of suggested usernames.
Build and run and register a new user.
Understanding Extension Properties
Similar to extension functions, Kotlin also supports extension properties. Extension properties are similar to extension functions, in that you can’t insert an actual member in a class.
To define an extension property, use this syntax:
val <T> List<T>.lastIndex: Int
get() = size - 1
This example code declares an extension property with the name lastIndex
that uses size
to calculate the last index of the list.
Since Kotlin isn’t inserting a member, there’s no way for the property to have a backing field. This means you can’t initialize the property explicitly or have a setter.
Extension properties can use only public members of the class to calculate values on the fly. This means the following isn’t allowed:
val <T> List<T>.lastIndex: Int = 1 //error
To read more, visit Kotlin’s official Backing Fields documentation.
Right now, the app shows thumb count and finger count separately from the logged-in hands. You’ll change this to a single total finger count, which combines both fingers and thumbs by using an extension property.
Open Extensions.kt and add the following extension property under TODO: 13
:
val Hand.totalFingers: String
get() {
return (fingersCount + thumbsCount).toString()
}
Now that you’ve created the extension property, open MainActivity and add this snippet below TODO: 14
, then add the required imports:
binding.userDescriptionTv.text = getString(R.string.user_description_total_fingers,
hand.bio, hand.totalFingers)
Also, remove the code that adds text to binding.userDescriptionTv
above the TODO: 14
, which looks like this:
binding.userDescriptionTv.text = getString(R.string.user_description,
hand.bio, hand.fingersCount, hand.thumbsCount)
In the above steps, you’ve changed the value of binding.userDescriptionTv
‘s text to use the extension property fingersCount
. This displays the combined thumb and fingers count.
Build and run and log in to see the updated UI: