Peter Aideloje I'm a passionate developer and technical writer whose interest aligns with full-stack software engineering, specifically Java, Csharp, and other frontend stacks like HTML5, CSS3, and JavaScript.

Comparing Kotlin scope functions

6 min read 1702

Kotlin Scoping Functions

The Kotlin programming language, which was designed for a Java virtual machine (JVM), has a combination of both object-oriented and functional programming features, as well as other programming paradigms. Used in Android development, Kotlin provides a unique feature known as scope functions, however, many developers run into some difficulty when dealing with these functions.

As an Android mobile developer, it is important to have a full grasp of this concept, which is a crucial part of application development. The beauty of Kotlin comes from unique features that make it suitable for both frontend and backend development. In this tutorial, we’ll cover the following:

To follow along with this tutorial, you’ll need the following:

Let’s get started!

What are scope functions?

In Kotlin, scope functions are used to execute a block of code within the scope of an object. Generally, you can use scope functions to wrap a variable or a set of logic and return an object literal as your result. Therefore, we can access these objects without their names. There are five types of scope functions in Kotlin: let, withrunapply, and also. Let’s consider these examples and their unique use cases.

There are many similarities between these five scope functions based on their similar operations, however, they differ in whether they return a lambda result or context object. They also vary in whether you refer to the context object using the this or the it keyword.

let function

The let function has numerous applications, but it is generally used to prevent a NullPointerException from occurring. The let function returns the lambda result and the context object is the it identifier. Let’s consider the following example:

fun main (){
val name: String? = null

println(name!!.reversed)
println(name.length)
}

In the code snippet above, we assigned a null value to the name variable. We then printed the reverse and the length of the string by including a NotNull assertion operator (!!) to assert that the value is not null because we have a nullable string name. Because we are calling the function on a null value, this results in a NullPointerException. However, we could prevent this by using the let function with the following code:

fun main (){

val name: String? = null

name?.let{
println(it.reversed)
println(it.length)
}
}

We place our code inside the lambda expression of the let function and replace the context object name with the it identifier. To prevent the NullPointerException, we include a safe call operator, ( ?.), just after our name object.

The safe call operator places a condition and instructs our program to execute the code only if the name object is NotNull. In this example, we do not need to use the NotNull assertion (!!).

We made a custom demo for .
No really. Click here to check it out.

Next, we’ll assign a string value “I love Kotlin” to our name variable. Then, we return this string value by saving our lambda value in a variable called lengthOfString:

fun main (){

val name: String? = "I love Kotlin!!"

val lengthOfString = name?.let{
println(it.reversed)
println(it.length)
}
println(lengthOfString)
}

with function

The with function has a return type as the lambda result, and the context object is the this keyword, which refers to the object itself. Let’s consider the example in the code snippet below:

class Person{
   var firstName: String = "Elena Wilson"
   var age: Int = 28
}
fun main() {
  val person = Person()
  println(person.firstName)
  println(person.age)
}

In the code snippet above, we created a Person class and assigned some properties, firstName and age. Next, in our main function, we printed out the values using println, which is used for cli output.

Let’s imagine that we had over twenty properties in the Person class, which would result in multiple code repetitions. We can correct this by using the with function and passing the person object in the lambda expression using the this keyword:

n){
 println(this.firstName)
 println(this.age)
}

The context object here refers to the person object on which the operation is performed. The return value of the with function is a lambda result. Imagine we decide to add ten years to the age and store the value in a variable called personInfo, which is of the type integer:

val person = Person()
val personInfo : String = with (person){
 println(this.firstName)
 println(this.age)
 age + 10
"I love the game of football"
}
println(personInfo)
}

The value produced is “I love the game of football”. In summary, the with function returns a lambda function and uses the this keyword as the context object.

run function

The run function returns the lambda result, and we refer to the context object by using the this keyword. The run function is a combination of the with and let functions. Let’s consider the example in the code snippet below:

fun main {

val person: Person? = Person()
val bio = person?.run {
 println(name)
 println(age)
"LogRocket rocks!!!"
   }
println(bio)
}

Assuming we decide to assign a null value to the person object, we’d have to prevent a NullPointerException from occurring. We can achieve this by calling the run function with the person object. Next, we’ll return the lambda function bio.

apply function

apply is a higher order function. The apply function returns a context object, and the context object returns this. Let’s consider the following example:

val car = Car()
  var carName: String = ""
  var carColor: String = ""

fun main {

 val car = Car().apply {
 carName = "Lamborghini"
 carColor = "Navy blue"
   }
}
 with(car){
 println(carName)
 println(carColor)
  }

also function

The also function is similar to the previous functions in that it is used to perform an operation on a particular object after it has been initialized. The also function returns the context object, and the context object can be referred to using the it identifier. Let’s refer to the code snippet below for further detail:

fun main(){

val numberList: mutableList<Int> = mutableListOf(1,2,4,5)
    numberList.also{
println("The list of items are: $numberList")

numberList.add(6)
println("The list of items after adding an element are: $numberList")
numberList.remove(4)

println("The list of items after removing an element are: $numberList")
    }
}

From the code above, we created a numbersList object with five integer values and performed some operations under the numbersList object. We then utilized the also function. Note that in the also function, we can refer to the numberList by using the it identifier, as seen in the code snippet below:

fun main(){

val numberList: mutableList<Int> = mutableListOf(1,2,4,5)
     val multipleNumbers = numberList.also {
println("The list of items are: $it")

it.add(6)
println("The list of items after adding an element are: $it")

it.remove(4)
println("The list of items after removing an element are: $it")
    }
println("The original numbers are: $numberList")
println("The multipleNumbers are: $multipleNumbers)
}

Another way to implement the also function is using the it and also keywords like in the code snippet below. We use the also function to modify the value of the firstName variable by assigning Eden Peter to it:

fun main {

 val person = Person().apply {
 firstName = "Eden Elenwoke"
 age = 22
   }
 with(person){
 println(firstName)
 println(age)
  }

person.also{
 it.firstName = "Eden Peter"
println("My new name is: + ${it.firstName}")
 }
}

When and how to use Kotlin scope functions

Using scope functions in the right place might seem a bit tricky at first, but it largely depends on what we want to achieve with project. Let’s refer the summary below as a guide to inform us on which scope function to use for each unique use case:

  • apply: You want to configure or initialize an object
  • with: You want to operate on a non-null object
  • let: You want to execute a lambda function on a nullable object and avoid NullPointException
  • run: You want to operate on a nullable object, execute a lambda expression, and avoid NullPointerException. This is the combination of the with and let function features
  • also: You want to perform some additional object operations and configurations

Comparing Kotlin scope functions with normal functions

Let’s compare a scope function and a normal function with a few examples. Let’s consider a normal function using a class named Student with three attributes, studentName, studentNumber, and studentAge, like below:

Class Student {
   var studentName : String? = null
   var studentNumber : String? = null
   var studentAge : Int? = null
}

With the code snippet below, we instantiate our class and assign values to it:

val student = Student ()
student.studentName = "Peter Aideloje"
student.studentNumber = 08012345678
student.studentAge = 28

Using a scope function can help us to achieve the same results as above in a simpler and cleaner way with less code. Let’s compare our expression above with a scope function in the code snippet below:

val person = Student().apply{
    studentName = "Peter Aideloje"
    studentNumber = 08012345678
    studentAge = 28
}

In the code snippet above, we instantiate the Student object and call the apply function. Then, we assign the studentName, studentNumber, and studentAge properties within the lambda expression.

When we compare the scope function and the normal function in the examples above, we notice that we successfully eliminated code repetition where the student object name was repeated multiple times. Using a scope function makes our code more concise and readable, and we initialized our properties without using the student object name.

Benefits of using scope functions

From the examples in the function comparison section above, we have come to realize some benefits of using scope functions:

  • Reduced boilerplate code
  • More concise and precise code
  • Reduced code repetition
  • Enhanced code readability

For further reading, you can also checkout the official Kotlin documentation.

Conclusion

In this article, we introduced the five scope functions in Kotlin. We also considered some unique use cases with examples, reviewing when to use each scope function. We compared scope functions with normal functions and finally reviewed the benefits of using scope functions.

As Android development continues to grow in popularity with more Android devices on the market, knowledge of the Kotlin programming language will become more crucial. I hope this article was helpful, and please feel free to leave a comment if you have any questions. Happy coding!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Peter Aideloje I'm a passionate developer and technical writer whose interest aligns with full-stack software engineering, specifically Java, Csharp, and other frontend stacks like HTML5, CSS3, and JavaScript.

Leave a Reply