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
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Thinking About Return Values

Early returns are useful to see the contract assertion up front. It’s clear when methods have one entry and one exit point. If you’ll be returning null, don’t scatter the return statements around in the middle of the code; these are hard to notice. If your code has a structure like this one:

if x
  if y
    return object
  else
    return null
...
return null

You can declare the nullable variable at the beginning of the method, update it along the way, and then return it at the end of the method instead, like this:

var object: Object? = null
if x
  if y
    object = ...
...
return object

Asserting Not-Null

Once you’ve spent time hardening your code as just discussed, there will be times you know for sure you’ve instantiated a value. You may have noticed that some of the code you’ve replaced uses !!. That’s Kotlin’s non-null assertion operator. It force-casts a nullable variable to a non-null one.

When using !!, if the variable is in fact null, you’ll get an NPE!

Inside the wipeFile method, you instantiate the random variable only when item == SHRED_MODE_RANDOM. Because there’s a check for this mode on line 83 you know for sure that you have instantiated random.

Remove that if check, and replace random.nextBytes(data) with this:

random!!.nextBytes(data)

Choose BuildMake Project. You’ll see that the code compiles, but in most cases, this is dangerous to use. Someone might change the flow of your code in the future, and they might not know to change all the places where you added !!.

As the complexity of a program increases, the edge cases that you originally thought won’t happen, starts to happen more often. There can be cases when a vulnerability isn’t obvious until the user enters something unexpected. In a way, the double exclamation mark is Kotlin yelling at you not to use it too often!! :]

Replace the line you just changed to the safer approach that you learned about earlier:

random?.nextBytes(data)

If you do use !!, here are some tips:

  • Cover all your code: Test each flow control case at least once. Perform stress and fuzz testing to see if there’s a state that causes it to be null that you’ve missed.
  • Don’t blindly trust data from external inputs. Sanitize and validate data from user input, serialized/unarchived object graphs and from network sources.
  • Declare and initialize the !! variable right before you use it, in order to reduce its scope.
  • Use each variable for exactly one purpose. That way, there’s less chance that other parts of the code will set that variable to something else, such as null!

These tips apply to development in general, but if your code has !!, assertion functions can help you out while you develop.

Testing With Assertions

Can you count how many NPEs in the universe haven’t happened yet? While some things remain undefined, Kotlin can at least make some assertions about reality. :]

The Kotlin.test framework has two valuable functions, assertNull and assertNotNull. If you’ve enabled JVM assertions, assert functions will throw an AssertionError when they evaluate to false. These functions are good for early bug catching while testing your code.

Besides asserts, there are a few other functions that you should know about:

  • require(Boolean) throws an IllegalArgumentException if you pass it an argument that evaluates to false. This is great to test the parameters of your methods.
  • requireNotNull returns the value if it’s not null; otherwise, it throws an IllegalArgumentException.
  • check(Boolean) throws an IllegalStateException error when it evaluates to false. This is great for testing app or object state.

Inside FileHelpers.kt file, add requireNotNull checks by replacing line 45 with the following:

it.listFiles()?.size ?: 0).also { theFileInfo ->
   requireNotNull(theFileInfo.path)
   requireNotNull(theFileInfo.fileType)
   requireNotNull(theFileInfo.name)
   requireNotNull(theFileInfo.extension)
   requireNotNull(theFileInfo.size)
}

With this code, you’re making sure that theFileInfo is valid.

Notice the use of also which is very similar to let, except that it returns itself. It’s a great way to chain together separate manipulations to the same object.

Just as with run verses let, also uses it inside the function block. If you want to use this to apply additional operations, you can use apply.

In MainActivity.kt file, replace the last statement on line 101 of the setupUI method with the code below:

fileStack.push(FileInfo(Environment.getExternalStorageDirectory().absolutePath,
    FileType.FOLDER,
    "/",
    0.0).apply { numberOfChildren = 0 })

You accessed numberOfChildren on this of the FileInfo instance.

You can do much more by using run, let, also and apply with the safe call operator.

You now have a whole lot of methods with slight differences at your disposal, so here’s a flowchart to help you decide which one to use:

Kotlin method choices

Delaying Initialization

You’ve now covered most scenarios when it comes to nullability! But here’s another case to consider: Given the way the Android Activity Lifecycle works, there are times you’ll need to declare a variable but not initialize it until later.

A great example is when you often initialize variables, such as an OnClickListener in the onCreate method.

It’s messy to declare the variable as nullable and then scatter safe call checks everywhere.

The solution is lateinit, which works on non-primitive objects to reduce the number of null checks you need to use. If you use it before you initialize it, you’ll get a kotlin.UninitializedPropertyAccessException.

It’s time to simplify the code in FileBrowserFragment.kt file. Change the recyclerAdapter definition at the top of the class to the following:

private lateinit var recyclerAdapter: RecyclerAdapter

Now that you declared recyclerAdapter as lateinit, find the instances of recyclerAdapter!! in the file and remove the unnecessary !! characters from the call:

Find and replace code

There are a few more alternatives to lateinit that you should know:

  • lateinit has a backing field with the same visibility as the property, so the backing field can still be set to null from Java. Delegates.notNull is a better choice in that case — var age: Int by Delegates.notNull().
  • If you use it before initializing it, Delegates.notNull throws an IllegalStateException.
  • lazy is a handy way to defer initializing a variable until you need it.
  • You define a block that’s not called until the first time you read the property.
  • The runtime saves the return value of that block for future access. The block will only run once.