Arpit
Arpit

Reputation: 1252

Kotlin : How to use if-else like condition with let block for 'null' checks

I am replacing all the null checks with let block in my code as part of a code review process.

1. Code example with null check:

if(someValue != null){//run if someValue is not null}
else {//run if someValue is null}

2. Code base after using let-run block if for null check,

var someValue : String? = null
someValue = "SOF"

someValue?.let {safeSomeValue->
//This code block will run only if someValue is not null
}?.run {
//This code block should run only when if someValue is null, like else condition
}

Now problem with let-run block is that both the code blocks are running even if the someValue is not null. So i am not able to replicate behaviour of if-else condition in code example 1 to run-let condition in code example 2.

Expected behaviour is to execute either let or run code block based upon if value is null or not-null.

Upvotes: 34

Views: 19780

Answers (6)

tim4dev
tim4dev

Reputation: 2987

Sometimes you will find the following useful

inline fun <T> T?.itOrNull(
    ifValue: (T) -> Unit,
    ifNull: () -> Unit
): Unit = when (this) {
    null -> ifNull()
    else -> ifValue(this)
}

usage:

data.title?.itOrNull(
   { view.text = it },
   { view.visibility = View.GONE }
)

Upvotes: 1

Arpit
Arpit

Reputation: 1252

Source - kotlinlang.org

  • ?. performs a safe call (calls a method or accesses a property if the receiver is non-null)
  • ?: takes the right-hand value if the left-hand value is null (the elvis operator)

Change ?. with ?: will solve this issue,

Code base as following, will run either let or run block based upon the null check.

var someValue : String? = null
someValue = "SOF"

someValue?.also {safeSomeValue->
//This code block will run only if someValue is not null
}?:run {
//This code block will run only if someValue is null, like else condition
}

Upvotes: 44

firefly
firefly

Reputation: 33

You can write two extension functions, eg: "ifSafe" and "ifNull", that can be used individually or chained together to mimic the if/else pattern:

fun <T> T?.ifSafe(block: (t:T?) -> Unit) = if (this!=null) block(this) else this
fun Any?.ifNull(block: () -> Unit?) = if (this==null) block().also{return null} else this

fun main() {

    var someValue = 4
    var nullValue = null
    
    someValue.ifSafe { safeSomeValue ->
        println("someValue: "+safeSomeValue.toString())
        null
    }.ifNull{
        println("someValue is null.")
    }
    
    someValue.ifNull{
        println("someValue is null.")
    }.ifSafe{ safeSomeValue ->
        println(safeSomeValue)
    }
    
    nullValue.ifNull{
         println("nullValue is null.")
    }
    
}

Upvotes: 0

Alexey Romanov
Alexey Romanov

Reputation: 170735

To give a very concrete example of what zsmb13's answer talks about:

val someValue = 0
someValue?.let { safeSomeValue->
    println("then")
    null
} ?: run {
    println("else")
}

prints both then and else. You can fix this by using also instead of let:

val someValue = 0
someValue?.also { safeSomeValue->
    println("then")
    null
} ?: run {
    println("else")
}

will only print then. It may be useful to read https://kotlinlang.org/docs/reference/scope-functions.html and figure out why, and to prove it's indeed always equivalent to the original if ... else. But I also agree with zsmb13 that it's probably a bad idea.

Upvotes: 16

zsmb13
zsmb13

Reputation: 89548

I am replacing all the null check with let block in my code

The first question here is, why? Is this somehow more readable for you than the regular if-else structure? I'd be generally wary of refactoring just for refactoring's sake.

The second consideration is much more important: this conversion you're doing is not equivalent to the original code, you're actually modifying the behavior with this change. Take the following piece of code:

var someValue : String? = null
someValue = "SOF"

someValue?.let {safeSomeValue->
    foo(someSafeValue)
    bar(someSafeValue)
} ?: run {
    println("shouldn't happen if someValue != null")
}

You expect the run block to execute only if someValue == null, but that's actually not the only case when it will run. The entire someValue?.let { ... } expression can produce null values not just when someValue itself was null, but also if the block passed to let returned null. In this case, if the bar() function call results in null, the run statement will be executed afterwards, therefore running both branches of what you thought was a fancied up if-else statement.

Upvotes: 16

Francesc
Francesc

Reputation: 29260

You can create an extension function, like this

fun <T> T?.whenNull(block: () -> Unit) = this ?: block()

then you call it like this

somevalue?.let { safeSomeValue ->
    // TODO
}.whenNull {
    // execute when someValue is null
}

Upvotes: 7

Related Questions