36

Delegated Properties in Kotlin1588451434547262

 5 years ago
source link: https://flutteryapps.com/blogs/kotlin/delegated-properties.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Delegated Properties in Kotlin

Kotlin has another amazing feature named Delegated Properties. Let’s see what is it and how can we take maximum out of it.

In this tutorial, we will see following points:

  1. What is the property?
  2. What is delegated property?
  3. Which are the standard delegated properties provided by Kotlin itself?
  4. How can we implement our own delegated properties?

What is the property?

  • Property is basically class level field which is declared either val (read-only/immutable) or var (mutable)
        class Tesla {
            val topSpeed = 200 // Immutable property
            var currentSpeed = 90 // Mutable property
        }

What is the delegated property?

  • It is class property, however, it delegates its setter or getter functionality to some another piece of code.
  • In simple words, you have a class property and it’s getter or setter or both managed by some other class.
  • It is implemented in a very simple way using by keyword after the class property name.
  • In the following example, topSpeed property’s value is given by SpeedoMeter class. (We will see in the 4th section the implementation SpeedoMeter class)
class Tesla {
    val topSpeed by SpeedoMeter()
}

Now let’s see Standard and most commonly used delegated properties provided by Kotlin in its library:

  • lazy
    • This property is used when you want to lazily initialize a property. Means, when you access it first time, then only it gets initialized and for later access, it will simply return the only cached value.
    • This property will not be initialized when the object of the class is created but only initialized when it is accessed the first time.
    • By default, initialization happens on the same thread (synchronized manner) and it is also thread-safe.
    • Of course, you can change initialization behavior with the following flags:
      1. LazyThreadSafetyMode.SYNCHRONIZED: This is provided by default. It makes sure that initialization can happen only from one thread.
      2. LazyThreadSafetyMode.PUBLICATION: From multiple threads, initialization can happen for uninitialized property and cached only the first return value from that.
      3. LazyThreadSafetyMode.NONE: Multiple threads can initialize and access the property. However, no locks are used so its behavior can be undefined.
    • Let’s say, you are using a repository with reference to DatabaseManager, NetworkClient, and FileManager. We can initialize these references with the lazy property. While logging-in a user, we will be needing only NetworkClient and DatabaseManager reference but not FileManager reference. So, NetworkClient and DatabaseManager will be initialized not FileManager.
class Repository {

    val networkClient by lazy {
        NetworkClient()
    }

    val databaseManager by lazy {
        DatabaseManager()
    }

    val fileManager by lazy {
        FileManager()
    }

    fun doLogin(user: User) {
        val response = networkClient.signUpUser(user)
        // .....
        // .....
        databaseManager.saveUser(response.user)
    }

}
  • storing
    • This property is used when you want to assign class properties’ value from a Map
    • However, the property name and map key name must be matching. We can use in JSON parsing or Reading values from Database columns to POJOs.
    • Demonstrating with the following simple example:
fun main(args: Array) {
    val blog = Blog(mutableMapOf(
        "title" to "Delegated Properties",
        "description" to "....",
        "url" to "https://flutteryapps.com/delegated-properties.html"
    ))

    print(blog)
    // It will print Blog(map={title=Delegated Properties, description=...., url=https://flutteryapps.com/delegated-properties.html})
}

data class Blog(val map: Map) {
    val title by map
    val description by map
    val url by map
}
  • observable
    • This property is used when you want to listen to every-time somebody updates a value of it.
    • On every-time, when value updates the function will be called and it will provide both the older value and newer value.
    • Let’s say, whenever the user changes the volume of the TV from remote then we want to listen to that and update UI based on volume level. It is easily done by observable delegate property. All we have to do is, give the initial value (in our case 7 volume level) and lambda function which has 3 params Property Info, Old value of volume, and New value of volume.
    • And whenever volumeLevel changes, the lambda that we passed will be called. Cool, isn’t it?
class Television {
    var volumeLevel by Delegates.observable(7, { propertyName: KProperty<*>, oldValue, newValue ->
        // update volume bar of UI from oldValue to newValue
    })
}
  • vetoable
    • This property is similar to observable property. However, observable property notifies after updating property value.
    • But, vetoable property notifies before updating the property. So, you can choose if the new value is a valid value or not. If you return true then only it changes the value.
class Television {
    var volumeLevel by Delegates.vetoable(7, { propertyName: KProperty<*>, oldValue, newValue ->

        if (newValue <= MAX_VALUE) {
            // update volume bar of UI from oldValue to newValue
            return@vetoable true
        }

        return@vetoable false
    })
}

How can we create our own custom delegated properties?

  • The time has come to see the implementation of SpeedoMeter class ;). But before that, let’s see what needs to do to implement delegated properties:
  • For Immutable/ReadOnly properties, the Delegate class has to implement the following method:

operator fun getValue(thisRef: R, property: KProperty<*>): T

  • For Mutable properties, the Delegate class has to implement both the following methods:
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
  • I know, above syntax is looking a bit scary. So, directly jumping to example:
fun main(args: Array<String>) {
    val tesla = Tesla()
    println(tesla.currentSpeed) // 90
    tesla.currentSpeed = 100
    println(tesla.currentSpeed) // 100
}

class Tesla {
    var currentSpeed by SpeedoMeter()
}

class SpeedoMeter {

    var speed = 90
    operator fun getValue(tesla: Tesla, property: KProperty<*>): Int {
        // After performing some heavy calculations
        return speed
    }


    operator fun setValue(tesla: Tesla, property: KProperty<*>, value: Int) {
        speed = value
    }

}
  • Here, Tesla class has a delegated property named currentSpeed and it is mutable property. So, SpeedoMeter has to implement both the getValue and setValue methods.
  • Delegate class can also implement interfaces named ReadOnlyProperty and ReadWriteProperty like the following:
interface ReadOnlyProperty {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

And one last thing, since Kotlin 1.1, we can also implement delegated properties for local variables. That’s it guys in delegated properties Kotlin.

Thanks for reading! Please share this article with others to spread knowledge.

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK