Skip to Content
Course content

13: Safe Calls and the Elvis Operator

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've already learned that Kotlin is obsessed with null safety. It's a good thing—I've spent far too many late nights in my career debugging NullPointerException crashes in Java that could have been avoided if the compiler had just yelled at me sooner. But once you start dealing with real-world data, you'll find that "nullable" isn't just a theoretical concept; it's everywhere.

Take a look at this snippet. Imagine we're building a user profile page, and we're trying to get the city where a user lives. Not every user provides an address, and even if they do, they might have left the city field blank.

data class Address(val city: String?)
data class User(val name: String, val address: Address?)

fun printUserCity(user: User) {
    // This won't even compile!
    println("User lives in: ${user.address.city}") 
}

Fighting the Compiler over Nested Nulls

If you try to run that code, Kotlin will stop you dead in your tracks. It'll tell you that user.address could be null, and you can't access .city on a null object. Now, the "lazy" way to fix this—the way I see juniors do when they're frustrated—is to use the !! (not-null assertion) operator.

println("User lives in: ${user.address!!.city}")

Please, don't do this. Using !! is basically you telling the compiler, "Trust me, I know what I'm doing," right before the app crashes in production because a user decided not to enter their address. It turns a compile-time safety feature into a runtime disaster.

Chaining with the Safe Call Operator

The professional way to handle this is with the Safe Call operator: ?.. Think of this as a conditional check that happens automatically. If the object on the left is null, the whole expression just evaluates to null and stops right there, rather than throwing an exception.

fun printUserCity(user: User) {
    val city = user.address?.city
    println("User lives in: $city")
}

In this version, if address is null, city becomes null. If address exists but city is null, city still becomes null. It's a clean way to "drill down" into an object hierarchy without writing five nested if (x != null) blocks. I personally love how this reads; it's almost like a path of possibility.

Providing Defaults with the Elvis Operator

The problem now is that city is a String? (nullable). Often, your UI can't just display the word "null" to a customer. You want a fallback value—something like "Unknown" or "Not Provided".

This is where the Elvis operator ?: comes in. It's called the Elvis operator because if you tilt your head, the ?: looks like a pompadour hairstyle. It essentially says: "Give me the value on the left, but if that's null, give me the value on the right instead."

fun printUserCity(user: User) {
    val city = user.address?.city ?: "Unknown City"
    println("User lives in: $city")
}

Now we have a guarantee. Because we provided a fallback string, the resulting city variable is no longer a String?—it's a regular, non-nullable String. You've safely navigated a nullable object, a nullable property, and provided a default value, all in one readable line.




📋 Practical Task

Exercise: The Weather Station Data Cleaner

You are writing a module for a weather app. The data coming from the API is messy; sometimes the temperature is missing, and sometimes the wind speed is missing. You need to clean this data before it hits the UI.

Your Goal: Implement the getWeatherReport function. It should return a formatted string. If the temperature is null, use "N/A". If the wind speed is null, use "Calm".

data class WeatherData(
    val temperature: Double?, 
    val windSpeed: Double?
)

fun getWeatherReport(data: WeatherData): String {
    // YOUR CODE HERE
    // Expected output format: "Temp: [value], Wind: [value]"
}

// Test cases to verify your logic:
fun main() {
    val complete = WeatherData(22.5, 12.0)
    val partial = WeatherData(22.5, null)
    val empty = WeatherData(null, null)

    println(getWeatherReport(complete)) // Should print: Temp: 22.5, Wind: 12.0
    println(getWeatherReport(partial))  // Should print: Temp: 22.5, Wind: Calm
    println(getWeatherReport(empty))    // Should print: Temp: N/A, Wind: Calm
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.