Waldo sessions now support scripting! – Learn more
App Development

Callback Functions in Kotlin: How to Use Them Effectively

Juan Reyes
Juan Reyes
Callback Functions in Kotlin: How to Use Them Effectively
March 29, 2022
8
min read

In this article, we'll explore the subject of callbacks in Kotlin. Callbacks are one of the most useful and valuable tools at your disposal to extend the functionality and flexibility of your code.

First, we'll go over what callbacks in general are and why they exist. Next, we'll explore the implementation of callbacks in Kotlin with some simple examples so you can learn the syntax. Then, we'll talk about what lambdas are and how to make callbacks asynchronous. Finally, we'll provide some recommendations on where to implement callbacks.

This article is for Kotlin developers. Therefore, we'll be working under the assumption that you have a basic understanding of Kotlin and Spring Boot.

If you haven't yet dipped your toes into Kotlin or any technology similar to it, we recommend you spend some time here first.

With that out of the way, let's move forward.

Preparing the Groundwork

Now, before we jump into the subject of callbacks, we want to make sure we're all on the same page. We'll be crafting our demo site in Spring Boot very quickly using the start.spring.io site.

Do you already know how to do this and just want to read the theory? In that case, you can skip this section.

Setup

If you're new to Java, all you'll need in your system is a Java Development Kit installed—either Gradle or Maven—and your favorite integrated development environment. In our case, we'll have VS Code as our IDE and Gradle.

As we mentioned above, to speed up the process, we'll start by going to https://start.spring.io and setting up our startup project. Before downloading the zip file, remember to select Spring Web as your dependency.

Home Controller

So, once you've downloaded what you need, go to the src/main/java/com/example/demo folder. Create a class file called HomeController.kt.

In it, just input the following code.

 
 
package com.example.blog
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping
@Controller
class HomeController {
    @GetMapping("/")
    fun home(model: Model): String {
        model["title"] = "Site"
        return "home"
    }
}

Now, continue to the src/main/resources/templates folder and create header and footer templates.

We've chosen the mustache template language for our templates, but you can make them in the language of your preference.

header.mustache

 
 
<html>
    <head>
        <title>{{title}}</title>
    </head>
    <body>

footer.mustache

 
 
</body>
  </html>

home.mustache

 
 
{{> header}}
<h1>{{title}}</h1>
<p>This is a test site.</p>
{{> footer}}

And, there you go! That's all you need.

You can now run the code with gradle bootRun in the terminal and see your website on localhost:8080.

Home page

Simple, right?

If you want an extensive explanation of what's going on behind the scenes, please check out the Kotlin community and this Spring Boot tutorial.

What Is a Callback?

As defined by Wikipedia, "In computer programming, a callback, also known as a 'call-after' function, is any reference to executable code that is passed as an argument to other code; that another code is expected to call back (execute) the code at a given time."

In essence, a callback is a code passed into a method or function as an argument. Then it's invoked inside the outer function to complete a routine or action.

This mechanism exists to allow the repurposing of code, reducing the need for boilerplate code and duplication.

Additionally, we use callbacks to continue code execution after completing an asynchronous operation. In this specific case, we refer to callbacks as asynchronous.

This structure is very common in webhooks and API services, where we can't halt the execution while waiting for a server response. Many modern web APIs, such as fetch, provide this kind of functionality on JavaScript.

Callbacks in Kotlin

Now that we understand what callbacks are, let's see some examples.

Here's a simple example of a callback.

 
 
package com.example.blog
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping
@Controller
class HomeController {
    @GetMapping("/")
    fun home(model: Model): String {
        model["title"] = buildTitle(::getTitle)
        return "home"
    }
    fun buildTitle(callback: (text: String) -> String): String {
        return callback.invoke("This site belongs to ")
    }
    fun getTitle(text: String): String {
        return text + "Juan"
    }
}

Notice how we added two new functions to the controller. The first function, buildTitle, receives the callback function. The second function, getTitle, provides the value. This first function indicates that it receives a parameter callback. This is a function that accepts a string (text) and returns a string. It's the primary key of our callback feature. It allows us to indicate what kind of function we're expecting and what it'll provide us.

We then invoke the callback function on our buildTitle, providing it with the text that it needs. Finally, the code returns the result to the method home we just called, which assigns it to the model object.

Regarding our getTitle function, its only purpose is to simply add the string "Juan" to whatever string you provided.

As you can see, there's nothing complicated.

Callback Block

However, what if you don't want to have that function necessarily defined on the controller class?

Well, you don't have to. You can define the structure of the getTitle function as a block directly at the invocation of the buildTitle function. Then, all you have to do is indicate the parameter that it will receive and add the code to execute.

 
 
package com.example.blog
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping
@Controller
class HomeController {
    @GetMapping("/")
    fun home(model: Model): String {
        model["title"] = buildTitle() { text ->
            text + "Juan"
        }
        return "home"
    }
    fun buildTitle(callback: (text: String) -> String): String {
        return callback.invoke("This site belongs to ")
    }
}

Notice that we removed the return directive in this case because code blocks explicitly return the last line of code.

Now, you might've noticed some particular things about the notation used for callbacks in Kotlin. For example, to indicate that a parameter passed to a function is a function, you must prefix it with :: (a double colon). Additionally, to invoke a callback provided as a parameter, you must use its invoke property instead of just invoking it like any other function in the class.

Thankfully, this is where the differences end in terms of the implementation of callbacks in Kotlin. And as you'll see in the next section, you have plenty of latitude and flexibility with Kotlin.

Lambda

What if you have a simple function that you feel is more appropriate to store in a variable to provide to multiple functions that use the callback?

Well, that's where lambdas come in handy.

A lambda is simply a code block that usually fits on a single line. It can be stored in a variable and provided to other functions or executed on the fly.

Here's a simple example of this.

 
 
package com.example.blog
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.ui.set
import org.springframework.web.bind.annotation.GetMapping
@Controller
class HomeController {
    @GetMapping("/")
    fun home(model: Model): String {
        var lmd = { text: String -> text + "Juan" }
        model["title"] = buildTitle(lmd)
        return "home"
    }
    fun buildTitle(callback: (text: String) -> String): String {
        return callback.invoke("This site belongs to ")
    }
}

Here, the lmd variable is our lambda and can be provided to the buildTitle function without the double colon :: indicator.

Asynchronous Callback

We've seen how to use callbacks in different ways. But so far, their usability has been quite limited and niche.

One of the main strengths of using callbacks is the ability to more easily determine and control when to execute certain parts of your code. And as we've described above, you might run into issues when you need to wait for another process to finish or have a problem with concurrent processes.

This is why you must use callbacks to provide a mechanism of asynchronicity and allow you to execute code later, when a response is received.

Either by using code blocks or lambda, you can specify particular instructions or behaviors to be executed after receiving a server response. This would allow you to, say, refresh a feed or update an input field.

Conclusion

As we've seen throughout this article, callbacks are one of the most fundamental and practical features of most modern programming languages. They allow a lot of latitude and versatility within our solutions. Kotlin has done a great deal to provide developers with a robust and versatile implementation of the mechanism that's easy to learn and intuitive to use.

However, overusing callbacks can lead to code cluttering and hard-to-spot bugs. That's why we encourage you to rely on Waldo's sophisticated and reliable testing solutions.

Waldo's behavioral replay engine can handle your application screens, variations in load time, and other common flakiness issues. And your team gets real-time, reliable insights on your product.

You can get a customized demo or check out the blog. Learn more here.

Automated E2E tests for your mobile app

Waldo provides the best-in-class runtime for all your mobile testing needs.
Get true E2E testing in minutes, not months.

Reproduce, capture, and share bugs fast!

Waldo Sessions helps mobile teams reproduce bugs, while compiling detailed bug reports in real time.