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.

Leave a rating/review
Download materials
Save for later
Share

Developers love Kotlin because it provides many features to make the code concise and their lives easy. Extension functions and extension properties are two such features.

Extension functions provide the ability to extend the functionality of existing classes without inheriting them. You can call these new functions in the usual way: as if they were methods of the original class.

You can’t access the private members — because these functions aren’t actually added inside the class — but they provide benefits that you’ll see later in this tutorial.

Throughout this article, you’ll work on a dummy social media app called Handbook — because it helps hands connect with each other. We’re coming for you, Facebook!

You’ll add new functionalities to existing classes. Along the way, you’ll learn about:

  • Using extension functions and extension properties.
  • Resolving them.
  • Their workings under the hood.
  • How to use them with nullable receivers and companion objects.
  • Different type of scopes for extensions.
Note: This tutorial assumes you have previous experience developing for Android in Kotlin. If you’re unfamiliar with Kotlin, take a look at our Introduction to Kotlin for Android tutorial. If you’re also new to Android development, check out our Getting Started With Android tutorials.

Getting Started

Click the Download Materials button at the top or bottom of the page to download the starter and final projects.

Open Android Studio and click Open an existing Android Studio project.

Android Studio 3.6 welcome wizard

Android Studio 3.6 new project wizard

Navigate to the starter project directory you downloaded and click Open.

Android Studio project selection window

Android Studio selection window box for starter project

Take some time to familiarize yourself with the code.

Project Structure

You’ll find the following packages and files in the starter project:

  • db: This package contains Constants.kt and HandsDb.kt.
  • Constants.kt: Contains enums for login and registration states.
  • HandsDb.kt: Contains code to save data locally for login, logout and registration features using SharedPreferences.
  • models: A package that contains Hand.kt, which is the data model to denote a user.
  • ui: This package has all the Activities the app uses.
  • utils: A package containing Extensions.kt, which is where you’ll write extension functions.

Now that you have an overview of the files in this project, build and run. You’ll see a screen like this:

Handbook app starting screen

Handbook app starting screen

Go through the Registration flow once to register your hand for this awesome platform:

Handbook app registration flow

Handbook app registration flow

Now, log out of the app and go through the login flow once:

Handbook app login flow

Handbook app login flow

Introducing Extension Functions

Extension functions are a cool Kotlin feature that help you develop Android apps. They provide the ability to add new functionality to classes without having to inherit from them or to use design patterns like Decorator. Read more about Decorator pattern on Wikipedia.

While working with the Android framework, you’ll find yourself writing helper classes — usually named Utils — which contain static methods that take instances of some class and perform operations on public members of that class. An example of such a pattern is:

fun showAlert(context: Context,
              message: String,
              length: Int = Toast.LENGTH_SHORT) {
  Toast.makeText(context, message, length).show()
}

Other parts of the code call this with an instance of Context by writing Utils.showAlert(context, "Uncool way"). This example function is more useful when you’re showing a Toast with a custom UI throughout the app, so you have one place to change it.

Other use cases include modifying third party classes when you want to add functionality that uses public members.

In such cases, extension functions come to the rescue. They provide a nice piece of syntactic sugar that lets you change how you call such methods so they look like regular member functions. For example:

context.showAlert("Cool way")

Extensions are syntactic sugar. They don’t actually modify the class, but they make new functions callable with the dot notation. To declare an extension function, you need to prefix the name of the function with a receiver type that you want to extend.

With that in mind, open Extensions.kt and under TODO: 1, add the following snippet:

fun ImageView.loadImage(imageUrl: String) {
  Glide.with(this)
      .load(imageUrl)
      .into(this)
}

If Android Studio shows a wizard to add required import statements in the class, click OK. Otherwise, add the following import statements:

import android.widget.ImageView
import com.bumptech.glide.Glide

In the above snippet, you’ve declared your first extension function. You’ve extended ImageView, which acts as a receiver type, and added an extension function named loadImage. The this keyword inside an extension function corresponds to the receiver object. In this case, it refers to an instance of ImageView.

This function uses the Glide library to load the image from a given URL into the ImageView.

Now, open OnBoardingActivity.kt and replace the code below TODO: 2 with the following:

binding.imageIcon.loadImage(getString(R.string.logo_url))

Add the following import statement if Android Studio doesn’t add it automatically:

import com.raywenderlich.android.handbook.utils.loadImage

This project uses view binding to work with XML-based views. You can check out Introduction to View Binding tutorial to learn more about it.

So, in the code above, you called loadImage(string) on the imageIcon ImageView as if it’s a member function of the class.

In the Onboarding screen, Glide loads the Handbook logo just like before. The only difference is the code now looks concise.

Build and run to see the screen below. If you’re logged in, log out to see the onboarding screen.

Handbook app starting screen using an extension function to load the logo

Handbook app starting screen

Resolving Extensions

To understand extension functions, you need to understand how to resolve them.

They’re dispatched statically. In other words, you determine the function that’s called by the type of expression that invokes the function, and not the resulting type at runtime. In a nutshell, they’re not virtual by receiver type.

To understand this better, open Extensions.kt and add the following imports:

import android.widget.Toast
import com.raywenderlich.android.handbook.R
import com.raywenderlich.android.handbook.ui.BaseActivity
import com.raywenderlich.android.handbook.ui.OnBoardingActivity

Now, add the following snippets below TODO: 3 and TODO: 4 respectively:

fun BaseActivity.greet() {
  Toast.makeText(this, getString(R.string.welcome_base_activity), Toast.LENGTH_SHORT).show()
}
fun OnBoardingActivity.greet() {
  Toast.makeText(this, getString(R.string.welcome_onboarding_activity), Toast.LENGTH_SHORT).show()
}

To give some context, OnBoardingActivity extends BaseActivity. You’re defining an extension function greet() with the same signature for both BaseActivity and OnBoardingActivity, but with different messages to show the user.

Next, open OnBoardingActivity and add this method below TODO: 5:

private fun showGreetingMessage(activity: BaseActivity) {
  activity.greet()
}

Also, add this below TODO: 6:

showGreetingMessage(this)

Phew! you’re done now. So the code you added in OnBoardingActivity defines showGreetingMessage, which calls the greet() extension function to greet the user with a toast when they start the app. It takes BaseActivity as a parameter.

Below TODO: 6, you called this method with this as argument, where this refers to the current instance of OnBoardingActivity. So you expect to see the toast with the message defined in R.string.welcome_onboarding_activity.

Build and run and you’ll notice the toast actually shows the message defined in R.string.welcome_base_activity instead:

App opening with a greeting message

App opening with a greeting message

The toast shows the string defined by R.string.welcome_base_activity and not the one you expected. That’s because the extension function depends on the declared type of the parameter, as discussed earlier, which is BaseActivity, and not the type that’s resolved at runtime, which is OnBoardingActivity.

What if there’s already a member function defined with the same name and signature as an extension function?

The Kotlin reference docs say that the member function always wins. If they have different signatures, however, Kotlin calls the extension function.

Open BaseActivity and, below TODO: 7, add the following snippet:

fun greet() {
  Toast.makeText(this, getString(R.string.welcome_base_activity_member),
    Toast.LENGTH_SHORT).show()
}

This code adds a member function with the same name and signature as your extension function in BaseActivity. Build and run:

Greeting toast from member function

App launching with toast greeting

The Toast now shows the message defined in the member function and not the extension function. So the Kotlin docs are correct. :]