Understanding Property Delegation in Kotlin

Most of the Java Developers are enjoying migrating from Java to Kotlin. Kotlin gives clean, concise, and efficient code. Also, it comes with a lot of advantages. One such advantage is Property Delegation. What is Property Delegation? And how is it useful for Kotlin Developers. Let’s dive into this article to learn about this.

Welcome to our MindOrks blog on “Property Delegation in Kotlin”. In this article, we are going to understand:

  • What is Property Delegation?
  • What are Default Delegated Properties in Kotlin?
  • How to create a custom Delegation Property?
  • How to use Kotlin Delegation Property with Android Development?

So, let’s get started!

Kotlin Property Delegation

A “Delegate” is just a class that provides the value of a property and handles its changes. This will help us to delegate(assign or pass on), the getter-setter logic altogether to a different class so that it can help us in reusing the code.

This is an alternative to inheritance property.

Kotlin Delegated Properties

Kotlin contains some inbuilt examples for Delegated Properties such as:

  • Lazy-value gets computed only upon first access
  • observable/vetoable-listeners get notified about changes to this property
  • Storing Properties in a Map (Instead of the separate field for each property)

To understand more about these built-in Kotlin Delegated Properties, you can refer the official documentation here

Since we got a basic understanding of what Property delegation in Kotlin is, let’s understand how to create our own Delegated Property

Custom Delegated Properties

For the case of simplicity, let’s take a very simple use-case. Let’s consider a scenario where we want a String property that always gets trimmed and has a common appended string after the original value.

The general way we would do this is:

var string: String = ""
    set(value) {
        field = "${value.trim()} is a String!"
    }
fun main() {
    string = "checking.....        "
    println(string)
}

Now, when we run the code, we get:

output: checking..... is a String!

We can see that the extra spaces are trimmed and the required string is appended.

Well, this is good for one variable. What if we have a bunch of string variables containing the same functionality?

We have to keep adding this setter property repeatedly:

var stringOne: String = ""
    set(value) {
        field = "${value.trim()} is a String!"
    }
var stringTwo: String = ""
    set(value) {
        field = "${value.trim()} is a String!"
    }
var stringThree: String = ""
    set(value) {
        field = "${value.trim()} is a String!"
    }
.
.
.
.

This meets our requirement but we see a lot of repetitive code. So, how can we resolve this?

Yes, the answer is Property Delegation!

Here, we shall “delegate” the property of trimming and appending the common string to a separate class so that we can reuse this wherever required.

Let’s understand this now step by step.

  • Let’s create a custom class, let’s name it TrimAppendDelegate
  • To use this class as a Delegate, we have to implement the ReadWriteProperty<> interface
  • Once we implement the interface, we have to implement the abstract members in the interface which are getValue and setValue
  • Finally, we define a private variable of String type(since we are defining a custom Delegate for our String property) inside our Delegate class and define the getValue and setValue properties
  • Now, the final class will look something like this:
class TrimAppendDelegate : ReadWriteProperty<Any, String> {
    private var trimAppendedString = ""
    override fun getValue(thisRef: Any, property: KProperty<*>) = trimAppendedString
override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        trimAppendedString = "${value.trim()} is a String!"
    }
}

Now, our delegate class is ready. How can we use this in our active class? It’s really simple:

private var trimmedString by TrimAppendDelegate()

That’s it! Now, if we try to initialize the trimmedString value, it will trim the string and append the required string at the end!

trimmedString = "This..      "
println(trimmedString)
Output: This.. is a String!

This is how we can achieve the property of Delegation.

Now, even if we have multiple strings, we can just add our property by using

by TrimAppendDelegate()

The code now looks Pretty and Neat with less Boiler Plate code! This is a great achievement for any Developer!

Now, that we have understood how to create custom delegates, let’s understand how useful this concept can be in Android Development.

Property Delegation in Android Development

Using Built-in delegated property Lazy:
Most of the time, we see the usage of lateinit var in our UI(Activities/Fragments) classes or View Models. We can use the concept of the lazy delegate in place of lateinit var. The variable will be initialized the first time it is used.

Using Built-in delegated property Observable:

  • Most of our Android applications use Recycler views. We know that every recycler view is associated with its respective adapter. Every time the data structure (list of objects) changes in the adapter, we call notifyDataSetChanged to update our Recycler view.
  • We can use the inbuilt delegated property “Observable” to get the old and changed values of the data structure(list of objects)
private var users: ArrayList<User> by Delegates.observable(arrayListOf()) { property, oldValue, newValue ->
    Log.d("Old value ${property.name} size", "${oldValue.size}")
    Log.d("New value ${property.name} size", "${newValue.size}")
    notifyDataSetChanged()
}
  • In the above code snippet, we can consider User as one of the model classes, and “users” is a list of User objects.
  • We can access the old and new values whenever the value for “user” changes
  • Finally, we can call notifyDataSetChanged, if there is a change in oldValue and newValue by comparison.

Note: To access the advantage of this Observable delegated property, the parameter should be a “var” instead of “val”. Else the changes cannot be identified, because, val, in Kotlin, is read-only.

Using Built-in delegated property Observable along with Lazy:

  • We can also use this “Observable” property along with “lazy” for updating our views. This can be very helpful if we are not using the concept of LiveData in our application
  • Let’s understand the code snippet
class MainActivity : AppCompatActivity() {
    
    private val textView: TextView by lazy { textview }
    private var text: String by Delegates.observable("") { _, _, newValue ->
        textView.text = newValue
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        text = "NewText"
    }
    
}
  • We have initiated textView with a lazy delegate just for our safety so that there will be no null pointer exception while accessing it.
  • Whenever there is a change in the “text” variable, the text view is updated with the new value
  • This is similar to the concept of Live Data.

Using Built-in delegated property Lazy along with Custom Delegate:

  • Let’s say we want to save a boolean shared preference in our MainActivity.kt file (Any activity class file)
  • Let’s create a custom delegate to store a Boolean shared preference value:
//Delegating the boolean preference saving option
class BooleanPreference(
    private val preferences: Lazy<SharedPreferences>,
    private val name: String,
    private val defaultValue: Boolean
) : ReadWriteProperty<Any, Boolean> {
    
    @WorkerThread
    override fun getValue(thisRef: Any, property: KProperty<*>): Boolean {
        return preferences.value.getBoolean(name, defaultValue)
    }
    override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean) {
        preferences.value.edit().putBoolean(name, value).apply()
    }
    
}
  • We can see that our custom delegate class takes three parameters, the preferences instance, the name(Key in this case), and the default value
  • So, let’s create our shared preferences global instance in our Activity file
private val prefs: Lazy<SharedPreferences> = lazy { // Lazy to prevent IO access to main thread.
    this.applicationContext.getSharedPreferences(
        PREFS_NAME, MODE_PRIVATE
    )
}
companion object {
    const val PREFS_NAME = "Preferences"
    const val TOGGLE_PREFS = "toggle"
}
  • Let’s say we are handling the preference of a toggle here in our Activity class. We can just use our custom delegate as follows:
private var togglePreference by BooleanPreference(prefs, TOGGLE_PREFS, false)
  • Now, let’s say if we want to change the value in the onCreate method(or any click listener, in general):
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    togglePreference = true
    Toast.makeText(this, "$togglePreference", Toast.LENGTH_SHORT).show()  // Shows true
}
  • We just use it as a simple variable assignment. Looks clean and concise!

That’s the beauty of property delegation in Kotlin.

That’s all about this article.

We hope that this article has been of some help in understanding property Delegation in kotlin.

Thank you so much for your time!

Keep Learning, Keep Sharing, Keep Supporting!

Team MindOrks!

Also, Let’s connect on Twitter, Linkedin, and Github