Null Safety Tutorial in Kotlin: Best Practices

In this tutorial, you’ll look at Kotlin’s nullability best practices. You’ll learn about null safety in Kotlin and how to avoid NPEs. By Kolin Stürt.

Leave a rating/review
Download materials
Save for later
Share

Does nothing exist? Or does it exist only in reference to something tangible? How can you divide a number of things among no things?

Nothing is a concept tied to language; in Kotlin, the closest relative is null. In order to write solid code, it’s important to understand the concept of null.

In this tutorial, you’ll look at Kotlin’s nullability feature and it’s best practices. During the process, you’ll learn about the following:

  • Nullable variables.
  • Null pointer exceptions.
  • How to make safe calls on objects.

Note: This tutorial assumes that you’re already familiar with the basics of Android development and Android Studio. If Android development is new to you, first read through the Beginning Android Development and Kotlin for Android tutorials.

Getting Started

Download and unzip the materials for this tutorial using the Download Materials button at the top or bottom of this page. Open and run the starter project in Android Studio 3.3.0 or higher to see what you’ll be working with. When you open the app for the first time you will be prompted for File access permission, simply choose Allow to grant file access for the app:

Allow file access permission

This app deletes files of your choice. Start using it by browsing through the various folders and tapping on a file. Once you select and tap the file, you’ll encounter this error:

Null Pointer Exception Crash

You’ll fix that error soon. But first, if you don’t have test files to delete, you can copy the sample text file included in the root folder of the materials, BlackHoles.txt, to your device. Yes, it’s a list of various black holes in the universe. :]

If you want to move test files to your physical device, set your device to file transfer mode:
File Transfer Mode

On Linux and Windows you can access the device as a regular drive. For Mac you’ll need Android File Transfer.

Finding a NullPointerException

This app crashes when you tap a file due to a null variable. If you’re a Java developer, you’ll already be familiar with the misery of NullPointerExceptions.

In Java, all variables except primitive variables actually store references to memory addresses. Because they are references, you can set the variables to null. When the system expects a reference to be valid but instead it’s null, it throws a NullPointerException, NPE for short.

If you haven’t implemented exception handling, the app questions the nature of reality, and then crashes.

Sad dinosaur

Kotlin aims at being a safer language. By default, variables are non-null references and cannot be set to null.

You can make variables nullable by adding ? to the end of the variable. Once you do this, the compiler tries to make sure you use the variable safely for the duration of its lifecycle by using compile-time checks.

While Kotlin attempts to eliminate NPEs, it doesn’t do away with them entirely. That’s why the best practice during development is to start with non-null variables at the narrowest possible scope. You should only change the variable to be nullable or move it to a broader scope if it’s absolutely necessary.

Note: NPEs can cause security vulnerabilities, especially when they happen in security-related code or processes. If attackers can trigger an NPE, they might be able to use the resulting exception to bypass security logic or cause the app to reveal debugging information that is valuable in planning future attacks. NPEs can also be considered security vulnerabilities if temporary work files aren’t cleaned up before the process terminates.

Performing Null Checks

Now that you understand NPEs, you can avoid problems by checking a variable for null reference before using it. It’s common to see multiple checks even when the variable is not expected to be null.

This is defensive programming — the process of making sure your app still functions under unexpected conditions. This is especially important in safety-critical apps.

Null checks are a good idea when you need to check for an uninitialized state or when you need to call UI setup only once.

Check out line 66 of MainActivity.kt. The isInitialized flag is attempting to track if you’ve started the activity for the first time or if you are recreating it, such as after an orientation change. It’s better to leave that logic up to the standard lifecycle behavior.

In the onCreate method definition, the savedInstanceState argument is of nullable type Bundle?. It is initialized to null by default the first time you create the activity.

Instead of trying to track this with an extra variable, you can check for null directly. Change line 66 to the following:

if (savedInstanceState == null) {

Search and remove the remaining two occurrences of isInitialized in the file:

Init flags

Using the Safe Call Operator

You’ve just seen a great example where checking for null makes sense. In many other cases, you end up with code that looks like this:

if (loggedInUser != null) {
 if (userToSend != null) {
   if (validator != null) {
     ...
     validator.canUserSendMessage(loggedInUser, userToSend)
     ...
   } //end if (loggedInUser != null)
 } //end if (userToSend != null)
} //end if (validator != null)

As you can see, it’s not immediately clear what the piece of code does without first weeding out the if-not-null checks.

Kotlin discourages the use of those checks, and instead introduces a safe call operator. Just as variable declarations use ? to mark variables as nullable, adding ? to a method will only allow the method to execute if the variable is non-null.

Check out the statements within the onResume and onPause methods in the FileBrowserFragment.kt file. Notice how you use the safe call operator on the context? object. You’ll apply the same operator to the arguments object in this app to prevent the crash you saw earlier.

In the onCreate method in the FileBrowserFragment.kt file, remove the first if-not-null check and replace the line right after it with this:

val filePath = arguments?.getString(ARGUMENTS_PATH)

This line calls getString only if arguments exists; otherwise, it returns null. filePath is now an inferred nullable val. See how the logic is clear all in a single line of code?

There’s no question that it is defined as a best practice to use the safe call operator in place of if-not-null checks!

Note: While the safe call operator is usually great for chaining calls together, it can sometimes hinder self-documenting code:
object?.first?.second?.third?.active().

If your code is starting to look like this, assign the complicated line to a variable for readability:
var isUserVerified = object?.first?.second?.third?.active()