197

Is there any way to chain multiple lets for multiple nullable variables in kotlin?

fun example(first: String?, second: String?) {
    first?.let {
        second?.let {
            // Do something just if both are != null
        }
    }
}

I mean, something like this:

fun example(first: String?, second: String?) {
    first?.let && second?.let { 
        // Do something just if both are != null
    }
}
Daniel Gomez Rico
  • 15,026
  • 20
  • 92
  • 162
  • 2
    Do you want N items, not just 2? Do all the items need the same type, or different types? Should all values be passed into the function, as list, or as individual parameters? Should the return value be a single item or a group of same number of items as input? – Jayson Minard Feb 20 '16 at 11:34
  • I need all arguments, can be two for this case but also wanted to know a way to do this for more, in swift is so easy. – Daniel Gomez Rico Feb 21 '16 at 16:39
  • Are you looking for something different than the answers below, if so comment what is the difference you are seeking. – Jayson Minard Feb 21 '16 at 17:38
  • How would it be to refer to the first "it" within the second let block? – Javier Mendonça Feb 15 '18 at 13:16

17 Answers17

221

Here are a few variations, depending on what style you will want to use, if you have everything of same or different types, and if the list unknown number of items...

Mixed types, all must not be null to calculate a new value

For mixed types you could build a series of functions for each parameter count that may look silly, but work nicely for mixed types:

inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
    return if (p1 != null && p2 != null) block(p1, p2) else null
}
inline fun <T1: Any, T2: Any, T3: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, block: (T1, T2, T3)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null) block(p1, p2, p3) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, block: (T1, T2, T3, T4)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null && p4 != null) block(p1, p2, p3, p4) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, T5: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, p5: T5?, block: (T1, T2, T3, T4, T5)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null && p4 != null && p5 != null) block(p1, p2, p3, p4, p5) else null
}
// ...keep going up to the parameter count you care about

Example usage:

val risk = safeLet(person.name, person.age) { name, age ->
  // do something
}   

Execute block of code when list has no null items

Two flavours here, first to execute block of code when a list has all non null items, and second to do the same when a list has at least one not null item. Both cases pass a list of non null items to the block of code:

Functions:

fun <T: Any, R: Any> Collection<T?>.whenAllNotNull(block: (List<T>)->R) {
    if (this.all { it != null }) {
        block(this.filterNotNull()) // or do unsafe cast to non null collection
    }
}

fun <T: Any, R: Any> Collection<T?>.whenAnyNotNull(block: (List<T>)->R) {
    if (this.any { it != null }) {
        block(this.filterNotNull())
    }
}

Example usage:

listOf("something", "else", "matters").whenAllNotNull {
    println(it.joinToString(" "))
} // output "something else matters"

listOf("something", null, "matters").whenAllNotNull {
    println(it.joinToString(" "))
} // no output

listOf("something", null, "matters").whenAnyNotNull {
    println(it.joinToString(" "))
} // output "something matters"

A slight change to have the function receive the list of items and do the same operations:

fun <T: Any, R: Any> whenAllNotNull(vararg options: T?, block: (List<T>)->R) {
    if (options.all { it != null }) {
        block(options.filterNotNull()) // or do unsafe cast to non null collection
    }
}

fun <T: Any, R: Any> whenAnyNotNull(vararg options: T?, block: (List<T>)->R) {
    if (options.any { it != null }) {
        block(options.filterNotNull())
    }
}

Example usage:

whenAllNotNull("something", "else", "matters") {
    println(it.joinToString(" "))
} // output "something else matters"

These variations could be changed to have return values like let().

Use the first non-null item (Coalesce)

Similar to a SQL Coalesce function, return the first non null item. Two flavours of the function:

fun <T: Any> coalesce(vararg options: T?): T? = options.firstOrNull { it != null }
fun <T: Any> Collection<T?>.coalesce(): T? = this.firstOrNull { it != null }

Example usage:

coalesce(null, "something", null, "matters")?.let {
    it.length
} // result is 9, length of "something"

listOf(null, "something", null, "matters").coalesce()?.let {
    it.length
}  // result is 9, length of "something"

Other variations

...There are other variations, but with more of a specification this could be narrowed down.

Milad Faridnia
  • 9,113
  • 13
  • 65
  • 78
Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • 1
    You could also combine `whenAllNotNull` with destructuring like so: `listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f")`. – dumptruckman Mar 27 '19 at 16:19
  • "..keep going up to the parameter count you care about" I'd say this is not the way – Farid Dec 22 '21 at 16:18
  • Arrow.kt has an implementation of th the first alternative: https://arrow-kt.io/docs/apidocs/arrow-core/arrow.core/-nullable/index.html – Marius K Dec 23 '21 at 15:26
98

If interested here are two of my functions for solving this.

inline fun <T: Any> guardLet(vararg elements: T?, closure: () -> Nothing): List<T> {
    return if (elements.all { it != null }) {
        elements.filterNotNull()
    } else {
        closure()
    }
}

inline fun <T: Any> ifLet(vararg elements: T?, closure: (List<T>) -> Unit) {
    if (elements.all { it != null }) {
        closure(elements.filterNotNull())
    }
}

Usage:


// Will print
val (first, second, third) = guardLet("Hello", 3, Thing("Hello")) { return }
println(first)
println(second)
println(third)

// Will return
val (first, second, third) = guardLet("Hello", null, Thing("Hello")) { return }
println(first)
println(second)
println(third)

// Will print
ifLet("Hello", "A", 9) {
 (first, second, third) ->
 println(first)
 println(second)
 println(third)
}

// Won't print
ifLet("Hello", 9, null) {
 (first, second, third) ->
 println(first)
 println(second)
 println(third)
}
Dario Pellegrini
  • 1,652
  • 14
  • 15
  • This is very nice, but I am still missing a case where I can use the first input in the second. Example: ifLet("A", toLower(first)) { //first = "A", second = "a" } – Otziii Aug 13 '19 at 11:18
  • Cause in the ifLet statement the first argument is not yet been unwrapped, a function like your is not possible. Can I suggest to use guardLet? It's pretty straight forward. val (first) = guardLet(100) { return } val (second) = guardLet(101) { return } val average = average(first, second) I know that is not what's you asked, but hope it helps. – Dario Pellegrini Aug 29 '19 at 12:28
  • Thanks. I have multiple ways of solving this, the reason for saying is that in Swift its possible to have multiple ifLets after each others separated by comma and they can use the variables of the previous check. I wish this was possible in Kotlin as well. :) – Otziii Sep 02 '19 at 10:08
  • 2
    It could be accepted answer, but there is overhead on every call. Because vm creates Function object firstly. Also considering dex limitation, this will add Function class declaration with 2 method references for every unique checks. – Oleksandr Albul Oct 01 '19 at 02:34
  • 1
    @OleksandrAlbul That is not correct. The vm does not create the function object becuase the function and all its passed in lambdas are inlined. Therefor their internal code is copied at compile time to wherever the function is used. The only overhead comes from the varargs which creates a collection. – Graham Sep 15 '20 at 20:01
  • 3
    Maybe I'm wrong, but it looks like for example when you pass different types into these functions, T will be deduced as any. So, the lambda variables first, second, third, are of type Any, which means you need to cast them back to do anything useful with them. – Nir Friedman Sep 16 '20 at 15:17
  • 1
    Making the return type `Unit?` and returning the closure result and null in the else condition allows for conditional chaining, e.g., `ifLet(...) { ... } ?: run { }` in full: `inline fun ifLet(vararg args: T?, closure: (List) -> Unit): Unit? = if (args.all { it != null }) closure(args.filterNotNull()) else null` – Tim Sylvester Mar 12 '21 at 19:22
  • How can we use it with two or more collection like List or MutableList that has different types? – Bitwise DEVS Jun 01 '22 at 02:43
  • @NirFriedman I just tried this answer with the `guardLet` and you are 100% correct. If whatever is on the left side of the equal sign isn't null, you have to cast it into a new variable with the correct data type. This answer is both good(optional chaining) and bad(extra casting) – Lance Samaria Jun 02 '23 at 04:46
14

You can write your own function for that:

 fun <T, U, R> Pair<T?, U?>.biLet(body: (T, U) -> R): R? {
     val first = first
     val second = second
     if (first != null && second != null) {
         return body(first, second)
     }
     return null
 }

 (first to second).biLet { first, second -> 
      // body
 }
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
yole
  • 92,896
  • 20
  • 260
  • 197
  • 4
    What if someone has 4 and the other 6 and the other 8 arguments to check?! Just keep adding those "fancy" arguments?! )) – Farid Dec 22 '21 at 16:19
  • @Farid, yes, but I would challenge why there are so many nullable properties floating around in the first place that this would even be an issue. – Tenfour04 Aug 31 '23 at 16:04
14

I like the idea of using a list filtering the null values, I usually do something similar when I'm working with the same type, but when there are multiple types, to avoid the values parsed as Any, I just do something like this:

fun someFunction() {
    val value1: String = this.value1 ?: return
    val value2: Int = this.value2 ?: return
    ...
 }

It works and for me is important to keep the type of safety.

Ramesh R
  • 7,009
  • 4
  • 25
  • 38
Max Cruz
  • 1,215
  • 13
  • 17
8

You can create an arrayIfNoNulls function:

fun <T : Any> arrayIfNoNulls(vararg elements: T?): Array<T>? {
    if (null in elements) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return elements as Array<T>
}

You can then use it for a variable number of values with let:

fun example(first: String?, second: String?) {
    arrayIfNoNulls(first, second)?.let { (first, second) ->
        // Do something if each element is not null
    }
}

If you already have an array you can create a takeIfNoNulls function (inspired by takeIf and requireNoNulls):

fun <T : Any> Array<T?>.takeIfNoNulls(): Array<T>? {
    if (null in this) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return this as Array<T>
}

Example:

array?.takeIfNoNulls()?.let { (first, second) ->
    // Do something if each element is not null
}
mfulton26
  • 29,956
  • 6
  • 64
  • 88
8

Actually, you can simply do this, you know? ;)

if (first != null && second != null) {
    // your logic here...
}

There's nothing wrong with using a normal null-check in Kotlin.

And it's far more readable for everyone who will look into your code.

Ramesh R
  • 7,009
  • 4
  • 25
  • 38
Grzegorz D.
  • 1,748
  • 15
  • 18
  • 54
    It won't be enough when dealing with a mutable class member. – Michał Klimczak Jul 07 '17 at 11:27
  • 4
    No need to give this kind of answer, the intention of the question is to find a more "productive way" of handling this, since the language provides the `let` shortcut to do these checks – Alejandro Moya Oct 23 '18 at 23:58
  • 3
    In terms of maintainability, this is my choice, even if tis not as elegant. This is clearly an issue that everyone runs across all the time, and the language should deal with. – Brill Pappin Apr 03 '20 at 16:08
  • 1
    Given the function parameter is not-mutable (as per the question), using `let` is not ideal as it introduces unnecessary temp variables, hence check null directly is straightforward and more efficient as @Gzegorz shared. https://medium.com/mobile-app-development-publication/kotlin-dont-just-use-let-7e91f544e27f?source=friends_link&sk=6d8d04c5c00613f20e20fe057b3ebd51 Upvote the answer. – Elye Sep 15 '20 at 09:43
  • I actually don't get why everybody tries to invent the most complicated, unreadable super extension function or whatsoever. I think if you need a class holding 50 variations of a method using overrides and other magic...that might indicate a big code smell. Keep it simple and stupid. – JacksOnF1re Nov 30 '20 at 22:09
  • i don't get why this won't be enough for mutable class members? @MichałKlimczak I am so new to Kotlin, sorry. – Okhan Okbay Sep 24 '21 at 09:32
  • 1
    @OkhanOkbay a mutable class member (a `var` declared in class body) can be e.g. asynchronously changed by different methods. Even though you checked if `first` variable is null, in the very next line it might be null again because it might have been changed by then. Kotlin compiler knows that and in the next line it will still be a nullable var. Using `first?.let { it.foo() }` is safer in this regard. However it is not an everyday concern and sometimes the expressiveness of the regular nullcheck is nice (as Grzegorz D. said). – Michał Klimczak Sep 24 '21 at 10:10
6

For the case of just checking two values and also not having to work with lists:

fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
    if (value1 != null && value2 != null) {
        bothNotNull(value1, value2)
    }
}

Usage example:

var firstString: String?
var secondString: String?
ifNotNull(firstString, secondString) { first, second -> Log.d(TAG, "$first, $second") }
Daniel Gomez Rico
  • 15,026
  • 20
  • 92
  • 162
Jonas H
  • 81
  • 1
  • 3
3

I actually prefer to solve it using the following helper functions:

fun <A, B> T(tuple: Pair<A?, B?>): Pair<A, B>? =
    if(tuple.first == null || tuple.second == null) null
    else Pair(tuple.first!!, tuple.second!!)

fun <A, B, C> T(tuple: Triple<A?, B?, C?>): Triple<A, B, C>? =
    if(tuple.first == null || tuple.second == null || tuple.third == null) null
    else Triple(tuple.first!!, tuple.second!!, tuple.third!!)


fun <A, B> T(first: A?, second: B?): Pair<A, B>? =
    if(first == null || second == null) null
    else Pair(first, second)

fun <A, B, C> T(first: A?, second: B?, third: C?): Triple<A, B, C>? =
        if(first == null || second == null || third == null) null
        else Triple(first, second, third)

And here's how you should use them:

val a: A? = someValue
val b: B? = someOtherValue
T(a, b)?.let { (a, b) ->
  // Shadowed a and b are of type a: A and b: B
  val c: C? = anotherValue
  T(a, b, c)
}?.let { (a, b, c) ->
  // Shadowed a, b and c are of type a: A, b: B and c: C
  .
  .
  .
}
Moshe Bixenshpaner
  • 1,840
  • 1
  • 17
  • 23
3

I have upgraded the expected answer a bit:

inline fun <T: Any, R: Any> ifLet(vararg elements: T?, closure: (List<T>) -> R): R? {
    return if (elements.all { it != null }) {
        closure(elements.filterNotNull())
    } else null
}

this makes this possible:

iflet("first", "sconed") {
    // do somehing
} ?: run {
    // do this if one of the params are null
}
1

I solved this by creating some functions that more or less replicates the behavior of with, but takes multiple parameters and only invokes the function of all the parameters is non-null.

fun <R, A, B> withNoNulls(p1: A?, p2: B?, function: (p1: A, p2: B) -> R): R? = p1?.let { p2?.let { function.invoke(p1, p2) } }
fun <R, A, B, C> withNoNulls(p1: A?, p2: B?, p3: C?, function: (p1: A, p2: B, p3: C) -> R): R? = p1?.let { p2?.let { p3?.let { function.invoke(p1, p2, p3) } } }
fun <R, A, B, C, D> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, function: (p1: A, p2: B, p3: C, p4: D) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { function.invoke(p1, p2, p3, p4) } } } }
fun <R, A, B, C, D, E> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, p5: E?, function: (p1: A, p2: B, p3: C, p4: D, p5: E) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { p5?.let { function.invoke(p1, p2, p3, p4, p5) } } } } }

Then I use it like this:

withNoNulls("hello", "world", Throwable("error")) { p1, p2, p3 ->
    p3.printStackTrace()
    p1.plus(" ").plus(p2)
}?.let {
    Log.d("TAG", it)
} ?: throw Exception("One or more parameters was null")

The obvious issue with this is that I have to define a function for each case (number of variables) I need, but at least I think the code looks clean when using them.

Jon
  • 504
  • 5
  • 6
1

You could also do this

if (listOfNotNull(var1, var2, var3).size == 3) {
        // All variables are non-null
}
Amy Eubanks
  • 138
  • 7
  • 1
    The compiler will still complain that it can't guarantee that the vars are not null – Peter Graham May 07 '20 at 23:11
  • It is a case where maintainability is involved. If you plan to make changes to code, you want to insure that any future changes you make don't turn that var to null after your null check. It even stops the case where you thought the variable wasn't changed, but is (such as a background thread changing the var after your null check). – John Glen Sep 10 '21 at 00:38
1

one more idea based on the answer by @yole

fun <T, U, R> Pair<T?, U?>.toLet(body: (List<*>) -> R): R? {
    val one = first
    val two = second
    if (one == null || two == null)
        return null
    return if (one is Pair<*, *>) {
        one.toLet { a ->
            body(listOf(a, listOf(two)).flatten())
        }
    } else {
        body(listOf(one, two))
    }
}

so you can do the following

(1 to 6 to "a" to 4.5).toLet { (a, b, c, d) ->
    // Rest of code
}

blanNL
  • 360
  • 1
  • 11
1

maybe it is a little late. But now exist a library that addresses this specific need. It is Konad; have look at the maybe section

I will report here an example usage from the doc:

val foo: Int? = 1
val bar: String? = "2"
val baz: Float? = 3.0f

fun useThem(x: Int, y: String, z: Float): Int = x + y.toInt() + z.toInt()

val result: Int? = ::useThem.curry() 
   .on(foo.maybe) 
   .on(bar.maybe) 
   .on(baz.maybe)
   .nullable

// or even 

val result: Result<Int> = ::useThem.curry() 
   .on(foo.ifNull("Foo should not be null")) 
   .on(bar.ifNull("Bar should not be null")) 
   .on(baz.ifNull("Baz should not be null"))
   .result
Ramesh R
  • 7,009
  • 4
  • 25
  • 38
Luca Piccinelli
  • 379
  • 1
  • 17
1

a bit late but Baeldung has an example for a custom let function two parameters:

inline fun <T1 : Any, T2 : Any, R : Any> let2(p1: T1?, p2: T2?, block: (T1, T2) -> R?): R? {
return if (p1 != null && p2 != null) block(p1, p2) else null 
}
0

For any amount of values to be checked you can use this:

    fun checkNulls(vararg elements: Any?, block: (Array<*>) -> Unit) {
        elements.forEach { if (it == null) return }
        block(elements.requireNoNulls())
    }

And it will be used like this:

    val dada: String? = null
    val dede = "1"

    checkNulls(dada, dede) { strings ->

    }

the elements sent to the block are using the wildcard, you need to check the types if you want to access the values, if you need to use just one type you could mutate this to generics

Alejandro Moya
  • 434
  • 6
  • 11
0

This is working for me

first?.let { it -> second }?.let {
     if(first == second) { // params are not optional here anymore with auto smartcast
         // do something
     } else {
         // do something else
     }
} ?: run {
   // one of them is null, so plan B :)
}

if you want just do something or plan B

first?.let { it -> second }?.takeIf { first == second }.apply {
   // do something
} ?: run {
   // one of them is null OR something else, so plan B :)
}
Hamid Zandi
  • 2,714
  • 24
  • 32
-3

You can simplify it with:

first?.second?.let {}

Maxim Akristiniy
  • 2,121
  • 2
  • 15
  • 20