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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Null Safety Tutorial in Kotlin: Best Practices
30 mins
- Getting Started
- Finding a NullPointerException
- Performing Null Checks
- Using the Safe Call Operator
- Making Use of Let
- Running with Run
- Converting to Safe Casts
- Analyzing Equality
- Using the Elvis Operator
- Designing by Contract
- Thinking About Return Values
- Asserting Not-Null
- Testing With Assertions
- Delaying Initialization
- Using Null in Collections
- Interoperating With Other Languages
- Nullability in Java
- Nullability in C++
- Where to Go From Here
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.
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:
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:
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:
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.
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.
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:
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()
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()