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
}
}
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:
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
}
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
}
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
}
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 collectino
}
}
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.
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
}
In the spirit of Kotlin's various *NotNull
and *OrNull
functions you can create an arrayOfNotNullOrNull
function to allow you to create an array from "multiple nullable variables" if all the variables are not null
and null
otherwise:
fun <T : Any> arrayOfNotNullOrNull(vararg elements: T?): Array<T>? {
for (element in elements) {
if (element == null) {
return null
}
}
return elements as Array<T>
}
You can then use it for a variable number of values with let
:
fun example(first: String?, second: String?) {
arrayOfNotNullOrNull(first, second)?.let {
// Do something just if both are != null
// e.g. val (notNullFirst, notNullSecond) = it ...
}
}
If you already have your nullable values in a collection then you could create a noNullsOrNull
extension function similar to kotlin.collections.requireNoNulls
but that returns null
instead of throwing an exception.
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") }
Actually, you can simply do this, you know? ;)
if (first != null && second != null) {
// your logic here...
}
There's nothing wrong in using a normal null-check in Kotlin.
And it's far more readable for everyone who will look into your code.