Introduction
Briefly described, delegated properties are simply functions for delegating access (reading and writing) to a property using the by keyword. To implement it, you need to define getValue or setValueoperators for the class. To help and simplify the creation of these operators, you can use ready-made interfaces like ReadWriteProperty, ReadOnlyProperty or ready-made functions from kotlin stdlib: notNull , lazy , observable , vetoable . Then we’ll go deeper, and I advise you to look at the database in the official documentation .
Storage in a map and delegation to another property
1. The getter and setter of one property can be delegated to another property
The delegated property can be
1 – top level property
2 – class property or extension
To delegate a property to another property, use the syntax ::MyClass::delegate
orthis::delegate
class MyClass(var myClassProperty: Boolean = false)
val clazz = MyClass()
var delegated by clazz::myClassProperty
var notDelegated = clazz.myClassProperty
In this example , clazz::myClassProperty
it’s the same as clazz.myClassProperty
, the only difference is that an additional instance is created for the notDelegated property, but not for delegated . This turns out to be a replacement for the manual definition of get() and set(value).
Complete delegation of access to a property, without additional logic, can rarely be useful in the office. doc gives an example of renaming and maintaining backward compatibility.
class MyClass {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
fun main() {
val myClass = MyClass()
// Notification: 'oldName: Int' is deprecated.
// Use 'newName' instead
myClass.oldName = 42
println(myClass.newName) // 42
}
As an example from the official documentation, we mark an old property with an old name with the Deprecated annotation and all requests will now be redirected to a property with a new name.
2. Using a map instance as a delegate for storing properties
class Properties(map: Map<String, String>) {
val name by map
val version by map
}
val properties = Properties(
mapOf(
"name" to "delegate testing",
"version" to "0.0.1"
)
)
fun main() {
println(properties.name) // delegate testing
println(properties.version) // 0.0.1
}
Maps can often be returned by simply parsing a json response or some config with parameters, and using a delegated property in this case will make the code more readable. In the example above, we take values from the map through string keys, which are property names.
Multiple receivers
For a single delegate we can have multiple getValue and setValue with different arguments thisRef: ContextType
, different method definitions will be called in different situations. This can be very useful; in the example below for Fragment and Activity, getValue will work differently, depending on the context.
class CachedPropertyDelegate {
operator fun getValue(
activity: Activity,
prop: KProperty<*>
): String {
return "delegated property from Activity"
}
operator fun getValue(
fragment: Fragment,
prop: KProperty<*>
): String {
return "delegated property from Fragment"
}
}
Extensions
The most non-obvious way to declare a delegate is to create an extension property, thanks to which we can take advantage of the advantage and brevity of delegates without cluttering our class with additional methods or classes in general.
class UserInfo(
val name: String,
val lastName: String
)
operator fun UserInfo.getValue(thisRef: Nothing?, property: KProperty<*>): String {
val fullName = "$name $lastName"
println("access to $fullName")
return fullName
}
fun main() {
val user = UserInfo("John", "Doe")
val fullName by user
println(fullName)
}
// output
// access to John Doe
// John Doe
Providers
For a delegate, you can override not only the getValue and setValue operators , but also provideDelegate . This function returns the instance of our delegate when defined with the keyword by
. By overriding this delegate, you can execute additional code, which can be very useful. There is also an auxiliary interfacePropertyDelegateProvider
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class FileFormat(val fileFormat: String)
private val directoryPath get() = System.getProperty("user.dir") + "\\src\\main\\kotlin\\testdir"
class TextWriterDelegate(private val text: String) {
private var file: File? = null
operator fun getValue(thisRef: Nothing?, property: KProperty<*>): String {
return file?.readText() ?: error("No such file")
}
operator fun provideDelegate(thisRef: Nothing?, property: KProperty<*>): TextWriterDelegate {
val format = property.findAnnotation<FileFormat>()?.fileFormat?.let { ".$it" } ?: ".txt"
val dir = File(directoryPath)
dir.mkdir()
file = File(dir, property.name + format)
file?.writeText(text)
return this
}
}
fun texting(action: StringBuilder.() -> Unit) = TextWriterDelegate(buildString(action))
/** */
@FileFormat("txt")
val appConfig by texting {
val properties = listOf(
"name" to "delegate testing",
"version" to "0.0.1"
)
properties.forEach {
appendLine("${it.first} = ${it.second}")
}
}
In the example above, a file is created with the name property appConfig.txt
and the lines of text that we wrote when declaring the delegate are written down.
About The Author: Yotec Team
More posts by Yotec Team