time

Working with delegated properties in Kotlin

The origin of delegation in programming is from object composition. Object composition is a way to combine simple objects to derive a complex one. Object compositions are a critical building block of many basic data structures, including the tagged union, the linked list, and the binary tree.

To make object composition more reusable (as reusable as inheritance), a new pattern is incorporated - the delegation pattern.

This pattern allows an object to have a helper object, and that helper object is called a delegate. This pattern allows the original object to handle requests by delegating to the delegate helper object.

Though delegation is an object-oriented design pattern, not all languages have implicit support for delegation (such as Java, which doesn't support delegation implicitly). In those cases, you can still use delegation by explicitly passing the original object to the delegate to a method, as an argument/parameter.

Over time, the delegation pattern has proven to be a better alternative of inheritance. Inheritance is a powerful tool for code reuse, especially in the context of the Liskov Substitution model. Moreover, the direct support of OOP languages makes it even stronger.

However, inheritance still has some limitations, such as a class can't change its superclass dynamically during program execution; also, if you perform a small modification to the super class, it'll be directly propagated to the child class, and that is not what we want every time.

Delegation, on the other hand, is flexible. You can think of delegation as a composition of multiple objects, where one object passes its method calls to another one and calls it a delegate.

For an example, think of the Electronics class and Refrigerator class. With inheritance, Refrigerator should implement/override the Electronics method calls and properties. With delegation however, the Refrigerator object would keep a reference of the Electronics object and would pass the method calls with it.

Kotlin 1.1 brought many updates; one of the important ones was delegated properties. There are three types of delegated properties:

  • lazy: Lazy properties are the ones evaluated first and the same instance is returned after them, much like a cache.
  • observable: The listener is notified whenever a change is made.
  • map: Properties are stored in the map instead of in every field.

Delegates are a great way to postpone object creation until we use it. This improves startup time (which is much needed and appreciated in Android).

First, we will work with the lazy delegate property. Simply put, this delegate can suspend the object creation until we first access it. This is really important when you are working with heavy objects; they take a long time to be created - for example, when creating a database instance or maybe dagger components. Not only this, the result is remembered and the same value is returned for subsequent calls for getValue() on this kind of delegated property.

First, let's see how to create a property through lazy initialization. The syntax is as follows:

val / var <property name>: <Type> by <delegate>

While creating a lazy delegate, we use by lazy, as shown:

class MainActivity : AppCompatActivity() {
    private val textView : TextView by lazy {
        findViewById<TextView>(R.id.textView) as TextView
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView.text="ABC"
    }
}

The lazy delegate initializes the object on its first access and stores the value, which is then returned for subsequent accesses.

Let's take a look at an example:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val button by lazy { findViewById<Button>(R.id.submit_button) }
    setContentView(R.layout.activity_main)
    button.text="Submit"
}

The preceding is a standard onCreate method of an activity. If you look carefully, we have set the button variable before the setContentView(..) method. When you run it, it runs perfectly. If you hadn't used lazy, it would have given a NullPointerException.

Another key thing to note is that, by default, the evaluation of lazy properties will be synchronized, which means the value is computed in one thread, and the rest of the threads will see the same value. There are three types of initialization: LazyThreadSafetyMode.SYNCHRONIZED, LazyThreadSafetyMode.PUBLICATION, LazyThreadSafetyMode.NONE.

Another useful delegate is the observable delegate. This delegate helps us observe any changes to the property. For example, let's take a look at a very basic implementation of the observable delegate:

fun main(args: Array<String>) {
    val paris = Travel()

    paris.placeName="Paris"
    paris.placeName="Italy"
}

class Travel {
    var placeName:String by Delegates.observable("<>") {property, oldValue, newValue ->
        println("oldValue = $oldValue, newValue = $newValue")
    }
}

This is the output:

oldValue = <>, newValue = Paris
oldValue = Paris, newValue = Italy

As we can see, the observable delegate takes in two things: a default value (which we specified as <>) and a handler, which gets called whenever that property is modified.

Let's now work with the vetoable delegate. It's a lot like the observable delegate, but with it, we can "veto" the modification. Let's look at an example:

fun main(args: Array<String>) {
    val paris = Travel()
    paris.placeName="Paris"
    paris.placeName="Italy"
    println(paris.placeName)
}

class Travel {
    var placeName:String by Delegates.vetoable("<>"){
        property, oldValue, newValue ->
            if(!newValue.equals("Paris")){
                return@vetoable false
            }
            true
    }
}

This is the output:

Paris

As you can see in the preceding example, if newValue isn't equal to "Paris", we will return false, and the modification will be aborted. If you want the modification to take place, you need to return true from the construct.

The vetoable() takes an initial value, which could be an empty list, and also an onChange callback, which is called before the change to a property is made. The callback returns true if the change is successful and false if it is vetoed.

Vetoable can be especially useful if you use it in the Recyclerview adapter. Generally, you would assign data to the list directly and may call notifyDatasetChanged, but this is highly inefficient, as it will result in loading all the data again. We can use vetoable to check whether the content is the same by matching the old value and new value and can veto modification if it is the same.

The observable delegated property can be used extensively in adapters. Adapters are used to populate data in some sort of list. Usually, when data is updated, we just update the member variable list in the adapter and then call notifyDatasetChanged(). With the help of observable and DiffUtils, we can just update the things that are actually changed, rather than changing everything. This results in much more efficient performance.

Sometimes, you create an object based on values dynamically, for example, in the case of parsing JSON. For those applications, we can use the map instance itself as the delegate for a delegated property. Let's see an example here:

fun main(args: Array<String>) {
    val paris = Travel(mapOf(
        "placeName" to "Paris"
    ))
    println(paris.placeName)
}

class Travel(val map:Map<String, Any?>) {
    val placeName: String by map
}

Here's the output:

Paris

To make it work for var properties, you need to use a MutableMap, so the preceding example might look something like this:

fun main(args: Array<String>) {
    val paris = Travel(mutableMapOf(
        "placeName" to "Paris"
    ))
    println(paris.placeName)
}

class Travel(val map:MutableMap<String,Any?>) {
    var placeName: String by map
}

The output will be the same.

What’s the difference between lazy and lateinit?

Both are used to delay the property initializations in Kotlin:

  • lateinit is a modifier used with var and is used to set the value to the var at a later point.
  • lazy is a method or rather say lambda expression. It’s set on a val only. The val would be created at runtime when it’s required.
val x: Int by lazy { 10 }
lateinit var y: String