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
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.
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.
Navigate to the starter project directory you downloaded and click Open.
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:
Go through the Registration flow once to register your hand for this awesome platform:
Now, log out of the app and go through the login flow once:
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.
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:
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:
The Toast now shows the message defined in the member function and not the extension function. So the Kotlin docs are correct. :]