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
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 Build ▸ Make 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:
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:
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.