Understand "by" Delegated Properties in Kotlin

Simple way to understand "by" operator (used for delegated properties) in Kotlin and the reasons to use it.

ยท

4 min read

Understand "by" Delegated Properties in Kotlin

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 viewModelsis 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:

Did you find this article valuable?

Support Vincent Tsen - AndroidDev Blog by becoming a sponsor. Any amount is appreciated!

ย