1. Overview
Null-safety is a built-in feature in Kotlin. It allows us to handle nullable values comprehensively.
In this tutorial, we’ll explore how to check nullable Boolean (Boolean?) values in if statements.
2. Introduction to the Problem
In Kotlin, there are no primitive data types such as int or boolean in Java. Instead, we have “boxed” primitive types like Int and Boolean.
Kotlin’s nullable Boolean type Boolean? is pretty similar to Java’s Boolean type. Both may have a true, false, or null value.
For example, if we want to test Java’s Boolean type in an if statement, we’d do something like:
Boolean b = ...;
if (b != null && b) {
/* Do something when if b is true */
} else {
/* Do something if b is false or null */
}
Now, we could “translate” it into Kotlin:
val b: Boolean? = ...
if (b != null && b) {
...
} else {
...
}
It works as if b is not null – Kotlin smartly casts Boolean? to Boolean.
However, if we declare the boolean variable as a mutable variable using var, the smart casting won’t work:
var b: Boolean? = ...
if (b != null && b) {
/* ^ compilation error:
Kotlin: Smart cast to 'Boolean' is impossible,
because 'b' is a mutable property that could have been changed by this time
*/
...
We can fix the if statement quickly: if (b != null && b!! ). However, b != null && b!! looks a little awkward and not easy to read.
So next, let’s see how to check the Boolean? type in if statements in a better way.
3. Comparing With a Value
A straightforward approach to checking nullable Boolean variables in if statements is comparing the variable with the expected value.
An example can explain this quickly:
var b: Boolean? = ...
if (b == true) {
/* Do something when if b is true */
} else {
/* Do something if b is false or null */
}
The check above routes the processing to two branches: the “true” and the “false or null” case. Comparing to “b != null && b!!“, “b == true” is easier to understand.
However, sometimes, we would like to handle three cases (true/false/null) individually. Then, of course, we can write something like if – else if- else logic to achieve it. An idiomatic way to do it in Kotlin would be using the when expression:
var b: Boolean? = null
when (b) {
true -> ...
false -> ...
else -> ...
}
So far, we’ve learned how to idiomatically check a variable in the Boolean? type in Kotlin.
Now, let’s take a step forward. In practice, we often want to do some work depending on a Boolean variable, such as the examples above. If we often need to do these routines in our application, we can create some functions to simplify our implementation.
Next, let’s see them in action.
4. Creating Some inline Functions
Usually, after we check a boolean variable, the subsequent processing may fall into two categories:
- If-Then-Do: do something without caring about the execution’s returned value, for example: if (b == true) println(“it’s true”)
- If-Then-Get: execute something and get the returned value, for instance: var result :Int? = if (b == true) 5 + 7 else null
Next, let’s create some functions to ease these implementations.
4.1. Creating If-Then-Do Functions
Let’s first look at the scenario where we don’t care about the returned value of the execution. We can create three functions to make implementing If-Then-Do routines easier:
inline fun doIfTrue(b: Boolean?, block: () -> Unit) {
if (b == true) block()
}
inline fun doIfFalse(b: Boolean?, block: () -> Unit) {
if (b == false) block()
}
inline fun doIfFalseOrNull(b: Boolean?, block: () -> Unit) {
if (b != true) block()
}
As we can see in the code above, we’ve declared three functions. Each function accepts two parameters, the nullable Boolean and a higher-order function block.
Since we’d like to ignore the returned value from the block function, we’ve defined block to return the Unit type.
If we take a closer look at the functions, their implementations are not new to us. We compare the Boolean? variable to a desired boolean value, if the condition is satisfied, we execute the block function.
It’s worth mentioning that we’ve declared them as inline functions for getting better performance.
These functions allow us to write the If-Then-Do routine in this form:
var b: Boolean? = ...
doIfTrue(b) {
doThis()
doThat()
}
4.2. Testing If-Then-Do Functions
Now, let’s write a test to verify whether our If-Then-Do functions work as expected:
val aList = mutableListOf(0, 1, 2, 3, 4, 5, 6, 7, 8)
doIfTrue(true) { aList[0] = -1 }; assertThat(aList[0]).isEqualTo(-1)
doIfTrue(false) { aList[1] = -1 }; assertThat(aList[1]).isEqualTo(1)
doIfTrue(null) { aList[2] = -1 }; assertThat(aList[2]).isEqualTo(2)
doIfFalse(true) { aList[3] = -1 }; assertThat(aList[3]).isEqualTo(3)
doIfFalse(false) { aList[4] = -1 }; assertThat(aList[4]).isEqualTo(-1)
doIfFalse(null) { aList[5] = -1 }; assertThat(aList[5]).isEqualTo(5)
doIfFalseOrNull(true) { aList[6] = -1 }; assertThat(aList[6]).isEqualTo(6)
doIfFalseOrNull(false) { aList[7] = -1 }; assertThat(aList[7]).isEqualTo(-1)
doIfFalseOrNull(null) { aList[8] = -1 }; assertThat(aList[8]).isEqualTo(-1)
We want to test three functions and have three different test cases for each function: true, false, and null. As the test function above shows, for simplicity, we’ve prepared a mutable list with nine elements.
The block function we pass to each test case is updating the element in the list at a unique index. After the function invocation, we verify whether the corresponding element in the list gets updated or not.
The test passes when we give it a run. Therefore, our functions work as expected.
4.3. Creating If-Then-Get Functions
Now that we understand the If-Then-Do functions, the If-Then-Get functions are pretty similar. The only difference is the block function returns the generic R type now:
inline fun <R> getIfTrue(b: Boolean?, block: () -> R): R? {
return if (b == true) block() else null
}
inline fun <R> getIfFalse(b: Boolean?, block: () -> R): R? {
return if (b == false) block() else null
}
inline fun <R> getIfFalseOrNull(b: Boolean?, block: () -> R): R? {
return if (b != true) block() else null
}
As the functions above show, here, for simplicity, we return a null value if the condition doesn’t match. In practice, we can adjust it depending on the actual requirement.
For example, we can make the block function return a nullable R (R?) type if required. Also, we can add one more parameter as the default value for the “else” case:
inline fun <R> getIfTrue(b: Boolean?, orElse: R? = null, block: () -> R?): R? {
return if (b == true) block() else orElse
}
As an example, we can use these functions in this way:
var b: Boolean? = ...
val someValue: Int? = getIfTrue(b){
val a = calcA()
val b = calcB(a)
return a + b
}
4.4. Testing If-Then-Get Functions
Again, we test the If-Then-Get functions with a predefined list.
We call our functions to get the element from the list by a unique index and verify if the result is expected:
val aList = listOf("one", "two", "three")
assertThat(getIfTrue(true) { aList[0] }).isEqualTo("one")
assertThat(getIfTrue(false) { aList[0] }).isNull()
assertThat(getIfTrue(null) { aList[0] }).isNull()
assertThat(getIfFalse(true) { aList[1] }).isNull()
assertThat(getIfFalse(false) { aList[1] }).isEqualTo("two")
assertThat(getIfFalse(null) { aList[1] }).isNull()
assertThat(getIfFalseOrNull(true) { aList[2] }).isNull()
assertThat(getIfFalseOrNull(false) { aList[2] }).isEqualTo("three")
assertThat(getIfFalseOrNull(null) { aList[2] }).isEqualTo("three")
If we execute the test above, it passes.
5. Conclusion
In this article, we’ve explored how to idiomatically check Boolean? type in if statements. We’ve also learned that, sometimes, Kotlin’s when expression can make our code easier to read.
Moreover, we’ve seen some convenient inline helper functions to fluently write If-Then-Do and If-Then-Get routines.
As always, the full source code used in the article can be found over on GitHub.