Table of Contents
What is a scope function in Kotlin?
In Kotlin, scope functions are a set of functions that allow concise and expressive ways to operate on objects within a certain scope. There are five main scope functions in Kotlin: let
, run
, with
, apply
, and also
. Each of these functions has its own unique characteristics, but they all help in simplifying and organizing code
What are the Kotlin Scope Functions?
There are five main scope functions in Kotlin: let
, run
, with
, apply
, and also
.
Choosing the right scope function in Kotlin depends on the specific use case and the goals you want to achieve. Each scope function has its own characteristics and use cases, so understanding their differences will help you decide which one to use in a particular scenario.
Use let
when:
You want to perform operations on a non-null object.You need to transform the object and return a result.You want to introduce a new variable scope.
val originalString: String? = "hello"
val result = originalString?.let {
// Operations on non-null object
it.length
}
println(result)
KotlinExplanation:
originalString?.let { ... }
checks iforiginalString
is non-null.- If it’s non-null, the block inside
let
is executed, and the result is the length of the string. - The result is printed, and if
originalString
is null, nothing happens.
Use run
when:
You want to execute operations on the object itself.You need to perform multiple operations and return a result.You want to isolate a block of code.
data class Person(val name: String, var age: Int)
val person = Person("John", 25)
val result = person.run {
// Operations on the object
age += 5
"$name is now $age years old"
}
println(result)
KotlinExplanation:
person.run { ... }
executes operations on theperson
object.- It increments the age by 5 and creates a string with the updated information.
- The result is printed.
Use with
when:
You want to execute multiple operations on an object, but without changing the object itself.You prefer a function-style call for improved readability.
data class Car(var brand: String, var model: String, var year: Int)
val myCar = Car("Toyota", "Camry", 2022)
val result = with(myCar) {
// Operations on the object
model = "Corolla"
year += 1
"My car is a $brand $model from $year"
}
println(result)
KotlinExplanation:
with(myCar) { ... }
allows multiple operations onmyCar
.- It updates the model and increments the year.
- The result is a string describing the modified car.
Use apply
when:
You want to initialize or configure properties of an object.You want to make changes to the object and return the modified object itself.
class Book(var title: String, var author: String, var pages: Int)
val myBook = Book("The Kotlin Guide", "John Doe", 200)
val modifiedBook = myBook.apply {
// Initialize or configure properties
title = "Mastering Kotlin"
pages = 300
}
println("Original Book: $myBook")
println("Modified Book: $modifiedBook")
KotlinmyBook.apply { ... }
initializes or configures properties ofmyBook
.- It creates a new object (
modifiedBook
) with the changes. - The original and modified books are printed.
Use also
when:
You want to perform additional actions on an object.You want to inspect or log information about the object without modifying it.You want to use it in a chain without affecting the final result.
val numbers = mutableListOf(1, 2, 3, 4)
val result = numbers.also {
// Additional actions on the object
it.add(5)
it.remove(2)
}.joinToString()
println("Modified List: $result")
KotlinExplanation:
numbers.also { ... }
performs additional actions on thenumbers
list.- It adds 5 and removes 2 from the list.
- The modified list is printed using
joinToString()
.
What is the difference between let and run Kotlin Scope Functions?
let
is mainly used for operations on the result of a function or property, while run
is used for scoping and executing operations on the object itself.
Which Kotlin Scope Functions accepts argument?
Among the Kotlin scope functions (let
, run
, with
, apply
, and also
), let
is the one that explicitly takes the result of the expression it operates on as an argument.
Here’s an example:
val result = "Hello, World!".let { stringValue ->
println(stringValue)
stringValue.length
}
KotlinIn this example, the lambda expression inside let
receives the value of “Hello, World!” as stringValue
, and you can perform operations on it within the lambda.
The other scope functions (run
, with
, apply
, and also
) don’t explicitly pass the result as an argument; they operate on the context implicitly.
Note:
it
is used withinlet
andalso
to refer to the object being processed.this
is used withinrun
,with
, andapply
to refer to the receiver object.
Let’s explore use cases for each scope function in the context of Android development.
1. let
– Null Check and Transformation:
Use Case: In Android, you often deal with nullable values, especially when working with views. Let’s say you have an EditText
where a user enters their name.
val userInput: String? = editText.text.toString()
val formattedName = userInput?.let {
// Perform operations on non-null input
it.trim().capitalize()
} ?: "Default User"
textView.text = "Hello, $formattedName!"
KotlinExplanation:
userInput?.let { ... }
checks ifuserInput
is non-null.- If non-null, it trims and capitalizes the input, providing a formatted name.
- If null, it uses a default value.
- The formatted name is then displayed in a
TextView
2. run
– Initializing Views in an Activity:
Use Case: When initializing UI components in an Android Activity, you might use run
to configure the properties of views.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val resultTextView = run {
val textView = TextView(this)
textView.textSize = 18f
textView.setTextColor(ContextCompat.getColor(this, android.R.color.black))
textView
}
// Now, use resultTextView in your layout.
// (e.g., add it to a LinearLayout or set it as the text view of a button)
}
}
KotlinExplanation:
run { ... }
initializes and configures aTextView
.- The properties like text size and text color are set.
- The resulting
TextView
is stored inresultTextView
and can be used in the layout.
3. also
– Logging and Side Effects:
Use Case: During development, you might use also
for logging or performing additional side effects.
class MyViewModel : ViewModel() {
private val repository = MyRepository()
fun fetchData() {
repository.fetchData()
.also { result ->
// Log the result for debugging
Log.d("MyViewModel", "Data fetched: $result")
}
.observeForever { data ->
// Update UI or perform other actions with the fetched data
}
}
}
Kotlinrepository.fetchData().also { ... }
logs the result of the data fetch operation.- The fetched data is observed, and UI updates or other actions can be performed based on the result.
“side effect” refers to any observable change that is not directly related to the return value of a function or expression. It’s an external impact or modification that occurs during the execution of code, influencing the state of the system.
When you write code, you often have a main goal, like performing a calculation or updating data. However, there can be additional actions or changes that happen as a result of your code, which may not be directly related to the main goal.
For example, if you’re writing code to update a user’s profile information, a side effect could be logging information for debugging purposes or sending a notification. These actions aren’t the primary focus of the code, but they happen as a consequence of the main operation.
4. with
– Customizing AlertDialog:
Use Case: When creating a custom AlertDialog
in Android, with
can be used to configure various aspects of the dialog.
class MyFragment : Fragment() {
fun showCustomDialog() {
with(AlertDialog.Builder(requireContext())) {
setTitle("Confirmation")
setMessage("Do you want to proceed?")
setPositiveButton("Yes") { _, _ ->
// Handle positive button click
}
setNegativeButton("No") { _, _ ->
// Handle negative button click
}
create().show()
}
}
}
Kotlinwith(AlertDialog.Builder(requireContext())) { ... }
configures the properties of theAlertDialog
.- It sets the title, message, and positive/negative button click listeners.
- The dialog is then created and shown.
5. also
– Logging and Modifying State:
Use Case: When processing user input in an Android app, also
can be used for logging and modifying the state.
class MyActivity : AppCompatActivity() {
fun processUserInput(input: String) {
input.also {
// Log the original input
Log.d("MyActivity", "Original input: $it")
}.let { trimmedInput ->
// Further operations on the trimmed input
val processedInput = trimmedInput.toUpperCase()
// Use the processed input in your app...
}
}
}
KotlinExplanation:
input.also { ... }
logs the original input.- The result of
input.also
(original input) is passed tolet
for further processing. - In this example, the input is trimmed and converted to uppercase for further use.
Kotlin Scope Functions Coding Challenges
Given a nullable string, transform it to uppercase if it’s not null, and return a default string otherwise.
Solution using let
:
fun transformString(input: String?): String {
return input?.let {
it.toUpperCase()
} ?: "Default String"
}
KotlinChallenge: Create a function that configures and returns a Person
object with a default name and age.
Solution using apply
data class Person(var name: String = "John", var age: Int = 30)
fun createDefaultPerson(): Person {
return Person().apply {
name = "Default Name"
age = 25
}
}
KotlinChallenge: Given a list of numbers, log each number and calculate the sum of the list.
Solution using also
and run
:
fun processNumbers(numbers: List<Int>): Int {
val sum = numbers.also {
it.forEach { number ->
// Log each number
println("Processing number: $number")
}
}.run {
// Calculate and return the sum
this.sum()
}
return sum
}
Kotlinnumbers: List<Int>
: The bag is callednumbers
, and it contains a bunch of numbers.numbers.also { ... }
: This part is like opening the bag (numbers
) and looking at each number one by one. The special thing you want to do with each number is written inside the curly braces ({ ... }
).it.forEach { number ->
: This is like saying, “For each number in the bag, do the following…”println("Processing number: $number")
: The special thing is to tell everyone about each number by printing a message. It’s like saying, “I’m processing this number: [number].”
.run { ... }
: After you’ve looked at each number and told everyone about it, now you want to do something with the whole bag. In this case, you want to calculate the sum of all the numbers.this.sum()
: This is like adding up all the numbers in the bag to find the total sum.
val sum = ...
: The result of all this is a total sum, and you put it in a special box (variable) calledsum
.return sum
: Finally, you say, “Here is the total sum after processing all the numbers!”
Challenge: Given a nullable Person
object, perform a series of operations if it’s not null, and return a default Person
otherwise.
Solution using run
and apply
:
data class Person(var name: String? = null, var age: Int? = null)
fun processPerson(input: Person?): Person {
return input?.run {
// Perform operations on non-null Person
apply {
name = name?.toUpperCase()
age = age?.plus(5)
}
} ?: Person("Default Name", 25)
}
KotlinChallenge: Given a list of numbers, filter out the even numbers and double the remaining ones.
Solution using let
and map
:
fun filterAndDouble(numbers: List<Int>): List<Int> {
return numbers.let { list ->
list.filter { it % 2 != 0 }.map { it * 2 }
}
}
KotlinExplanation:
numbers.let { list ->
: Start a scope usinglet
and give the list a name (list
).list.filter { it % 2 != 0 }
: Filter out the even numbers from the list.list.map { it * 2 }
: Double each remaining number in the filtered list.- The final result is a list of doubled odd numbers.
Challenge: Create a function to customize and return an AlertDialog
with a title and message.
Solution using with
:
fun createCustomAlertDialog(context: Context): AlertDialog {
return with(AlertDialog.Builder(context)) {
setTitle("Important Message")
setMessage("This is a custom alert dialog.")
setPositiveButton("OK") { _, _ ->
// Handle positive button click
}
setNegativeButton("Cancel") { _, _ ->
// Handle negative button click
}
create()
}
}
KotlinExplanation:
with(AlertDialog.Builder(context)) {
: Start a scope usingwith
and create anAlertDialog.Builder
.setTitle("Important Message")
: Set the title of the alert dialog.setMessage("This is a custom alert dialog.")
: Set the message of the alert dialog.setPositiveButton("OK") { _, _ ->
: Set a positive button with a click listener.setNegativeButton("Cancel") { _, _ ->
: Set a negative button with a click listener.create()
: Create and return the customizedAlertDialog
Challenge: Given a string, count the occurrences of each character and return a map.
Solution using run
and groupBy
:
fun countCharacters(input: String): Map<Char, Int> {
return input.run {
groupBy { it }
.mapValues { it.value.size }
}
}
KotlinExplanation:
input.run {
: Start a scope usingrun
with the string as the receiver.groupBy { it }
: Group the characters in the string based on their values.mapValues { it.value.size }
: For each group, map the character to its count.- The final result is a map representing the count of each character.
Challenge: Given a list of names, extract the first names and capitalize them.
Solution using let
and map
:
fun extractAndCapitalizeNames(names: List<String>): List<String> {
return names.let { list ->
list.map { it.substringBefore(" ").capitalize() }
}
}
Kotlinnames.let {
: Start a scope usinglet
with the list of names as the receiver.list.map { it.substringBefore(" ").capitalize() }
: For each name, extract the first name and capitalize it.- The final result is a list of capitalized first names.
Challenge: Write a function to calculate the factorial of a given non-negative integer.
Solution using run
and recursion:
fun calculateFactorial(n: Int): Long {
return n.run {
if (this <= 1) 1L else this * calculateFactorial(this - 1)
}
}
KotlinExplanation:
n.run {
: Start a scope usingrun
with the integer as the receiver.if (this <= 1) 1L else this * calculateFactorial(this - 1)
: Perform a recursive factorial calculation.- The final result is the factorial of the given integer.
Challenge: Given a list of numbers, filter out the odd numbers and calculate their sum.
Solution using let
and filter
:
fun sumOfFilteredOddNumbers(numbers: List<Int>): Int {
return numbers.let { list ->
list.filter { it % 2 != 0 }
.sum()
}
}
KotlinExplanation:
numbers.let {
: Start a scope usinglet
with the list of numbers as the receiver.list.filter { it % 2 != 0 }
: Filter out the odd numbers from the list.sum()
: Calculate and return the sum of the filtered odd numbers.- The final result is the sum of odd numbers.
Question 1: Which Kotlin scope function is used when you want to perform operations on a non-null object and return a result?
a) run
b) let
c) with
d) apply
Explanation: b) let
In Kotlin, let
is used when you want to perform operations on a non-null object and return a result. It provides a concise way to transform or process an object and return the result of the lambda expression.
Question 2: In which Kotlin scope function do you often see the usage of it
as the default name for the lambda parameter?
a) run
b) let
c) also
d) apply
Explanation: b) let
The it
keyword is often used as the default name for the lambda parameter in the let
scope function. It refers to the object on which the function is invoked.
Question 3: When using the apply
scope function, what does it return?
a) Modified object
b) Result of the last operation
c) Original object
d) Result of the lambda expression
Explanation: c) Original object
The apply
scope function returns the original object on which it is applied. It is commonly used for configuring or initializing properties of an object.
Question 4: Which scope function is often used for initializing properties of an object?
a) run
b) let
c) with
d) also
Explanation: d) also
The also
scope function is often used for initializing properties of an object. It allows you to perform additional actions on the object and returns the original object.
Question 5: In Kotlin, which scope function is suitable for performing additional actions on an object without changing it?
a) let
b) also
c) run
d) apply
Explanation: b) also
The also
scope function is suitable for performing additional actions on an object without changing it. It returns the original object and is often used for side effects.
Question 6: Which scope function is commonly used for chaining operations on a nullable object?
a) let
b) also
c) run
d) with
Explanation: a) let
The let
scope function is commonly used for chaining operations on a nullable object. It allows you to execute a block of code if the object is not null.
Question 7: In Kotlin, what does this
refer to when used within a scope function?
a) Result of the lambda expression
b) The object itself
c) Modified object
d) Default name for the lambda parameter
Explanation: b) The object itself
In Kotlin scope functions, this
refers to the receiver object on which the function is applied. It is used within the context of the current object.
Question 8: Which scope function is often used for executing operations on an object itself without the need to reference the object explicitly in each line of code?
a) let
b) also
c) run
d) with
Explanation: c) run
The run
scope function is often used for executing operations on an object itself without the need to reference the object explicitly in each line of code.
Question 9: When using also
in combination with another scope function, what does also
return?
a) Modified object
b) Result of the last operation
c) Original object
d) Result of the lambda expression
Explanation: c) Original object
The also
scope function returns the original object. It is often used in combination with other scope functions to perform additional actions without changing the object.
Question 10: Which scope function is suitable for scenarios where you want to apply multiple changes to the same object?
a) run
b) let
c) with
d) apply
Explanation: d) apply
The apply
scope function is suitable for scenarios where you want to apply multiple changes to the same object. It returns the modified object and is often used for configuration.
In Kotlin, which scope function is used when you want to perform operations on an object and ignore the return value?
a) run
b) let
c) also
d) apply
Explanation: c) also
The also
scope function is used when you want to perform additional actions on an object without changing it and ignore the return value. It is often used for side effects.
Question 12: When using the with
scope function, what is the receiver object implicitly?
a) this
b) it
c) receiver
d) obj
Explanation: a) this
When using the with
scope function, the receiver object is implicitly referenced by this
. It is a concise way to perform operations on an object without using the object reference explicitly.
Question 13: Which scope function is commonly used in extension functions?
a) run
b) let
c) also
d) apply
Explanation: a) run
The run
scope function is commonly used in extension functions. It allows you to execute a block of code on the receiver object and implicitly references the object as this
.
Question 14: What does the it
keyword refer to in the context of the let
scope function?
a) Result of the lambda expression
b) The object itself
c) Modified object
d) Default name for the lambda parameter
Explanation: b) The object itself
In the context of the let
scope function, the it
keyword refers to the object on which the function is invoked. It is the default name for the lambda parameter.
Question 15: Which scope function is often used for conditional execution based on the object’s state?
a) run
b) let
c) also
d) apply
Explanation: b) let
The let
scope function is often used for conditional execution based on the object’s state. It allows you to execute a block of code only if the object is not null.
Kotlin Functions
Advanced Functions | – Higher-Order Functions – Lambda Functions – Extension Functions – Infix Functions- Generic Functions |
Functions | – Simple Functions – Function Parameters – Function Return Types – Function Scope |
Functional Programming Functions | – Immutability and Pure Functions- Function Composition – Functional Operators |
Feel free to follow and join my email list at no cost. Don’t miss out — sign up now!
Please check out my earlier blog post for more insights. Happy reading!
The Innovative Fundamentals of Android App Development in 2023