Understand "by" Delegated Properties in Kotlin
Simple way to understand "by" operator (used for delegated properties) in Kotlin and the reasons to use it.
When I first learned Kotlin, the by
operator is an alien to me. What the hell is that? In this article, I'm going to provide a simple example to show the reasons why we want to use this by
operator.
Custom Property get() and set()
Let's say I want to create a custom property get()
and set()
to print out something when the property value is read and set. I will do something like this.
class PropertyAccessExample {
var value: String? = null
get() {
println("property get value: $field")
return field
}
set(value: String?) {
println("property set value from $field to $value")
field = value
}
var anotherValue: String? = null
get() {
println("property get value: $field")
return field
}
set(value: String?) {
println("property set value from $field to $value")
field = value
}
}
fun main() {
val example1 = PropertyAccessExample()
// property set
example1.value = "example1"
// property get
println(example1.value)
}
Output:
property set value from null to example1
property get value: example1
example1
Please note that the field
here is an implicit field. To learn more about Properties and Fields in Kotlin, refer to this article.
The problem of this is I need to implement this custom get()
and set()
for all the properties that I would like to monitor. For example, anotherValue
above is the boilerplate code. To reduce the boilerplate code, we use by
operator - delegated properties.
"by" Delegated Properties
To implement the by
operator, we need to implement the delegated class that has implemented getValue()
and setValue()
operator.
class PropertyDelegate<T>(private var value: T? = null) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
println("property get value: $value")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
println("property set value from ${this.value} to $value")
this.value = value
}
}
class PropertyDelegateExample {
var value: String? by PropertyDelegate()
var anotherValue: String? by PropertyDelegate()
}
fun main() {
val example2 = PropertyDelegateExample()
// property set
example2.value = "example2"
// property get
println(example2.value)
}
Output:
property set value from null to example2
property get value: example2
example2
This line - var value: String? by PropertyDelegate()
basically means, "the property value is provided by PropertyDelegate()
delegated class. PropertyDelegate()
takes responsibility to set and get the value
property.
Please also note that the anotherValue
boilerplate code is now removed!
Common Usages of "by" Operator
by lazy
The most common usage of delegated property in my opinion is by lazy. I personally prefer val
over var
. So I use by lazy
a lot whenever is possible instead of using lateinit var
private val navController: NavController by lazy {
findNavController()
}
lazy
is a delegated property that is responsible to set the navController
. lazy
takes in lambda function and that last line is the return value of NavController
. Once navController
is accessed, the value will be cached and findNavContorller()
won't be called again in the subsequence accesses.
by viewModels
by viewModels
is also another very common usage of delegated property used to create the ViewModel
.
private val viewModel by viewModels<MainViewModel> {
MainViewModelFactory(application)
}
by viewModels
is similar to, by lazy
which is specific for ViewModel
creation. It takes in lambda function and the last line is the return value of the implementation of ViewModelProvider.Factory
interface. For more detailed usages, you can refer to my previous article here.
by mutableStateOf
by mutableStateOf
is what I learned recently while working on Jetpack Compose project.
Method 1 - MutableState<T>
(backing property)
In ViewModel
class, instead of using MutableState<T>
directly,
private val _snackBarStringIdState: MutableState<Int?> = mutableStateOf(null)
val snackBarStringId
get() = _snackBarStringIdState.value
// setting the value in ViewModel class
_snackBarStringIdState.value = R.string.no_internet
we can use by mutableStateOf
Method 2 - by mutableStateOf
(private set)
var snackBarStringId: Int? by mutableStateOf(null)
private set
// setting the value in ViewModel class
snackBarStringId = R.string.no_internet
Both methods are similar. The only difference is setting the value, as you can code see in the example above.
Conclusion
There are other delegated properties such as observable and storing properties, and you can see the example here. Since I seldom use 2 of them, I do not mention them here. I will update the "Common Usages" section above when I find them useful one day.
There are also class delegation using by
operator to delegate your class implementation to another object.
To see more info about it, read the following article: