Kotlin: Scope functions
8th December 2021
I'm talking about scope functions.
There are 5 of them and if you've spent any time in a Kotlin project you've definitely seen them:
let
, with
, ,
run
and also
.
They are also known as lambdas with receivers. I will only look at the first 3 as they are the most common.
They all allow you to execute code on an object but differ in how that object is treated within the scope of the block.
with
Takes the first argument to a receiver of the lambda that is the second argument. You can access the receiver explicitly using this. In most cases though, this is unnecessary.
The return value is the lambda result which is why you'll see the toString()
call after the curly braces.
data class DeliveryAddress(
var firstName: String,
var addressLine1: String,
var postCode: String,
var mobileNumber: String) {
var surname: String? = null
var addressLine2: String? = null
var addressLine3: String? = null
var phoneNumber: String? = null
override fun toString(): String {
return with(StringBuilder()) {
append("$firstName $surname,").appendLine()
.append(addressLine1).appendLine()
.append(addressLine2).appendLine()
.append(addressLine3).appendLine()
.append(postCode).appendLine()
.append(phoneNumber).appendLine()
.append(mobileNumber)
}.toString()
}
}
apply
Apply works much the same except that it always returns the object passed to it. It works like an extension function on the object and is useful as a Builder of objects.
fun buildAddress(): DeliveryAddress {
return DeliveryAddress("Hoinzey",
"line 1",
"Posty",
"148984019820").apply {
surname = "Jones"
addressLine2 = "SA"
addressLine3 = "Lot 3"
phoneNumber = "2222"
}
}
let
The return value is the result of the lambda. The context object is available in the lambda as it
,
I most commonly use it to execute blocks of code on non-null values
val map = mutableMapOf()
//stuff happens
val result = map[i]?.let {
//do something
}
Build your own
Below shows you the syntax differences between defining a standard lambda and one which has a receiver.
fun buildStringTakingLambda(builderAction: (StringBuilder) -> Unit): String {
val sb = StringBuilder()
builderAction(sb)
return sb.toString()
}
fun printFromLambda() {
println(buildStringTakingLambda {
it.append("hello")
it.append(" world")
})
}
fun buildStringLambdaWithReceiver(builderAction: StringBuilder.() -> Unit): String {
val sb = StringBuilder()
builderAction(sb)
return sb.toString()
}
fun printFromLambdaWithReceiver() {
println(buildStringLambdaWithReceiver {
append("Receiving")
append(" World")
})
}