Why Kotlin? An Introduction. (Part 2)

java.lang.NullPointerException at Main.subTitle(Main.java:847)

Why Kotlin? An Introduction. (Part 2)

java.lang.NullPointerException at Main.subTitle(Main.java:847)

In the first part of this series I stated I'd very likely start all my new Android projects in Kotlin. Don't get me wrong, I don't hate Java, but why would I make such a bold claim? The answer is null. Or more specifically, the explicit detraction of the use of null that Kotlin promotes. Kotlin has some nice features, but its handling of null variables is what hooked me.

Every developer is familiar with the dreaded NullPointerException (NPE). Most NPEs can be eliminated through discipline and good software development practices. But even the best of us every now and then forgets to do a null check and some data that we expected to be initialized wasn't, and our program comes crashing down around us (hopefully well before the application goes to production).

Kotlin by default forces you to explicitly state a variable is nullable at the time it is declared. This might seem annoying, and at first it is, but you'll quickly learn to embrace the idea (and trust me, its easier than shotgunning your code base with @NotNull and @Nullable annotations). Lets take a quick look at a sample here:

// Results in a compile error because it is null
var catchyHeader : String 
// A valid declaration with implicit type
var catchyHeader = "Something implicitly funny" 
// A valid declaration explicit type
var catchyHeader : String = "Something explicitly funny" 

But what if you need null? Too easy:

// Specify a nullable type with `?` 
var catchyHeader : String? = null

That's cool and all, but Android Studio lets me know when I'm using a null variable. But does it though? Consider this:

private String someData = BlackBoxLibrary.getData();

Do you trust this? Android Studio won't warn you, but you shouldn't:

public class BlackBoxLibrary {
    public static String getData() {
        String[] data = {null, "Data"};
        return data[(int) new Date().getTime() % 2];
    }
}

getData() returns null if the current time is even, "Data" if odd.

Yes, this is deliberately bad code, but how often and how closely do you look at your library's implementation?

Ain't Nobody Got Time For that

The problem here is trust and the problem with trust is time. The more I can trust a code base, the less time it takes me to develop a solution.

Overcoming Your Trust Issues

As pointed out, in Java we can't just infer trust on code. Eventually we'll get bit by null, so we end up filling our codebase with null checks and handling them in unique ways depending on what we're doing. I'm pretty sure everyone who has spent time writing Java has written a null check on some code where there's no physical way for the data to be null, but you did it just in case or because you couldn't be sure.

Kotlin overcomes this by embracing types as Nullable (explicitly) or Non-Null (implicitly) and requires you to handle these references appropriately in order to compile. Kotlin won't even let the previous scenario happen:

class BlackBoxLibrary {
    fun getData() : String {
        val dataArray = arrayOf(null, "data")
        val index : Int = (Date().time % dataArray.size).toInt()
        // Compile error; returning type `String?`(nullable) not `String`(non-null)
        return dataArray[index] 
    }
}

dataArray is implicitly set to Array<String?> (nullable) because of the null value in the initialization. If we change the line to val dataArray = arrayOf("something", "data"). The implicit type of dataArray becomes Array<String> (non-null), and our BlackBoxLibrary will compile.

Now that I know I can trust BlackBoxLibrary, using it becomes simple. Rather than:

private void doSomething() {
    String someData = BlackBoxLibrary.getData();
    if(someData == null) {
        someData = "default value";
    }    
    if(someData.contains("somevalue")) {
        // do something with the data
    }
}

We can write:

private fun doSomething() {
    val someData = BlackBoxLibrary().getData()
    if(someData.contains("data")) {
        // do something
    }
}

Yes, we've only saved 3 lines of code, but we also reduced our example code by 40% while increasing readability. Of course this isn't completely representative of a real code base, but the reality isn't far off. Think about the time you have to spend error handling and testing to ensure code functionality. A simple ? or lack thereof can save you and your team hours.

What if I NEED null!!?

It is unrealistic that to expect everything to always be non-null, but nullable values should be the exception, not the rule. Enter the !!, ?, and ?: operators and the… lateinit modifier.

This nullable variable shouldn't be null!!

Sometimes a variable can't be initialized, but eventually is and at the time of use, a null state is the result of something that went wrong. Say hypothetically, you call a REST API and receive a 200 response, where the documentation guarantees data in the body of the response. HTTP response bodies can obviously be empty, so this has to be a nullable type, but it shouldn't be. This might be a situation where you want that NPE to get thrown if you see a null body, so we use !! which will essentially cast our nullable type to non-null.

fun doRestCall() : String {
    val response = restService.getDataFromServer() // result is never null
    if(response.code == 200) {
        val body = response.body // this might be null
        // This compiles because `!!` casts body to non-null. It will throw an NPE at Runtime if the body is empty     
        return body!!
    }
}

As doRestCall() returns, we have assurance that the value is non-null now.

What if I want to handle the null?

Even easier, use ?. This is especially useful for retrieving nested objects where anything in the chain can be null:

fun processAircraftArrival(airCraft : AirCraft?) : String {
    var arrival = aircraft?.destination?.arrivalTime
    if(arrival == null) {
        return "Unknown"
    }
    return arrival
}

Now we have the ability to use and check null objects in a chain. The equivalent in Java would be:

public String processAircraftArrival(AirCraft aircraft) {
    if(aircraft != null 
        && aircraft.getDestination() !== null 
        && aircraft.getDestination().getArrivalTime() != null) {
        return aircraft.getDestination().getArrivalTime();
    }
    return "Unknown";
}

We can even make the Kotlin code more simple with the ?: Elvis operator.

The Elvis operator as implemented in Kotlin is basically a ternary null check:

fun processAircraftArrival(airCraft : AirCraft?) : String {
    var arrival = aircraft?.destination?.arrivalTime
    // If arrival is not null return arrival otherwise "Unknown"
    return arrival ?: "Unknown"
}

As seen, the Elvis operator has allowed us to knock off another three lines of code from the example and (at least in my opinion) even easier to read.

This is awesome! But wait… how do you declare a null variable?

Some variables can't be instantiated because their state isn't known until after an object is constructed. While we wish this wasn't true, it is inevitable. Some things have to be declared up front as null, so if you haven't figured it out by this point, here you go:

// A valid null assignment. Note the `?` making this a nullable type.
var myNullString : String? = null
// Invalid null assignment. Even though the variable is null, it still needs to be instantiated with a null value
var myInvalidNull : String?

And now the question that's obviously been on your mind… “How does this work with dependency injection?”

Suspense Gif You want dependency injection with the safety of an assured non-null object, right? The answer: lateinit

@Inject
lateinit var injectedObject : SomeObject

Note, if a lateinit object isn't initialized, it will throw an NPE. You're kind of overriding the null assurance by using lateinit to tell the application that the variable will be assigned at runtime. This is fine if used by design, but beware the ability to abuse it.

That's All for Now

This series isn't going to be a fireworks show where I save the best for last. I'm putting it right here in this post. There are other good and bad features to share, but this is the one that really got me hooked, and hopefully it already has you thinking about diving deeper. Be on the lookout for more to come in the near future and feel free to message me or comment on the board if you have any questions or comments.

comments powered by Disqus