Kotlin Scope Functions Explained [Example]

By definition, Scoped functions are functions that execute a block of code within the context of an object.

Kotlin “scope functions” are functions that allow for changing the scope, or the range, of a variable. There are five such functions included in the Kotlin standard library: apply, run, with, let, and also.


before getting started into scope functions, i have explained about the basic of kotlin in details.

Beginning Android Development with Kotlin

koin – Kotlin Dependency Injection In Android

Ktor – Networking Client For Android


Scope Functions

Object References

To use these functions, we must understand a couple of things.

Inside a lambda of a scope function, this (lambda receiver) is used to access the context object. The same object can be accessed by using it (lambda argument). In general, both can perform similar things.

This

Inside a scope function, you will be able to reference the context object by a short word (this), instead of the name itself.

run, apply, with use ‘this’ to access the context object.

Note: We can exclude this keyword to refer to members of the class.

fun main() {
    var user = User("Joe","London")
    val username = with(user) {
        name = "Palani"
        address = "India"
        println("Name = $name , Address = $address")
        name
    }
}

data class User(var name: String, var address: String)

It

let’ and ‘also’ functions refer to the object’s context as a lambda argument.

fun main() {
    var user = User("Joe","London")
    user.let {
        println("Name : ${it.name}")
        println("Address : ${it.address}")
    }
}

data class User(var name: String, var address: String)

Return values

There are two types of return values that a scope function can return:

Lambda result

If we write any expression at the end of the code block, it becomes the return value for the scope function. Return value for ‘let’, ‘run’, and ‘with’ functions is the lambda result.

fun main() {
    var user = User("Joe","London")
    val updatedUsername = user.let {
        it.name = "John"
        it.name
    }
    println("Updated Username : $updatedUsername")
}

data class User(var name: String, var address: String)

Context object

apply’ and ‘also’ functions return the context object itself. In this case, we don’t need to specify the return value. The context object is automatically returned.

fun main() {
    var user = User("Joe","London")
    val updatedUser = user.apply {
        name = "John"    
    }
    println("Updated Username : ${updatedUser.name}")
}

data class User(var name: String, var address: String)

Check the below table for the comparison.

ItThis
Return Resultletrun, with
Return objectalsoapply

let’s see the kotlin scope functions in detail.

Let

Mostly used for null checks, when applying ?.let on an object, we can rest safe that every time we access that object inside the scope function, the object will be not null. To reference the object inside the scope function, we use the keyword it.

  • Context Object – it
  • Returns – last statement
  • Use case – let function is often used to provide null safety calls. Use safe call operator(?.) with ‘let’ for null safety. It executes the block only with the non-null value.
fun main() {
    
    var user:User? = null
    user?.let {
        println("First Time : User $user")
    }
    user = User("John","India")
    user?.let {
        println("Second Time : User $user")
    }
}

data class User(var name: String, var address: String)

Run

‘run’ function can be said as the combination of ‘let’ and ‘with’ functions.

  • Context Object – it
  • Returns – last statement
  • Use Case – Used when the object lambda contains both initialization and the computation of the return value.  Using run we can perform null safety calls as well as other computations.

It is the only scope function that has two variants.

run as extension

used to create a scope to run an operation over an object, and get a result.

fun main() {
    val user = User("mani","India")
    val username = user?.run {
        name = generateFullName(name)
        name
    }
    println(username)
}

fun generateFullName(name: String) : String {
    return name + " kandam"
}

data class User(var name: String, var address: String)

Note: run returns the last statement of the function.

run as function

reduce the scope of certain variables, to avoid unnecessary access.

fun main() {
    val user = User("mani","India")
    
    val username = run {
        generateFullName(user.name)
    }
    println(username)
}

fun generateFullName(name: String) : String {
    return name + " kandam"
}

data class User(var name: String, var address: String)

With

Similar to apply function, with is also used to change properties of an instance i.e. object configuration. The only difference is with is not an extension function. The last expression of with function returns a result.

  • Context Object – this
  • Returns – last statement
  • Use Case – Run multiple operations on an object
fun main() {
    val user = User("mani","India")
    
    val username = with(user) {
    	name = "Sathish"
        address = "USA"
        name
    }
    
    println(username)
    
}

data class User(var name: String, var address: String)

Apply

Basically, if you are initializing an object, and setting a bunch of properties like in this case, you have a pretty solid candidate to apply this scope function.

  • Context Object – this
  • Returns – same object
  • Use Case – Initialize and configure an object
fun main() {
    val user = User("mani","India")
    
    val updatedUser = user.apply {
        name = "Siva"
        address = "London"
    }
    
    println(updatedUser)
    
}

data class User(var name: String, var address: String)

Also

A common use also is for side effects without modifying the object. We can use it for doing some operations on the intermediate results. also does not transform the object. It returns the same object.

  • Context Object – it
  • Returns – same object
  • Use Case – It is used where we have to perform additional operations when we have initialized the object members.
fun main() {
    val user = User("mani","India")
    
    val updatedUser = user.apply {
        name = "Siva"
        address = "London"
    }.also {
        println(it)  
    }
   
}

data class User(var name: String, var address: String)

That’s it. Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *