2

Is there a more idiomatic way to write the following?

foo?.let{
        if(!foo.isBlank()) {
            bar?.let { 
                if(!bar.isBlank()) {
                    println("foo and bar both valid strings")
                }
            }
        }
    }

basically this the idea is that both strings should be nonNull and nonEmpty and I was wondering if there is a more Kotlin way than doing if(foo.isNullOrEmpty && !bar.isNullOrEmpty)

Jim
  • 3,845
  • 3
  • 22
  • 47
  • scope functions cannot help you here. Kotlin encourages us to prefer concise forms first:, thus `isNullOrEmpty` is the best choice. – lotor Oct 30 '19 at 20:28

6 Answers6

3

Use this

fun <T, R, S> biLet(lhs: T, rhs: R, block: (T, R) -> S): S? = if (lhs != null && rhs != null) block(lhs, rhs) else null

Use as

biLet(foo, bar) { safeFoo, safeBar ->
}

Edit: variant for strings

fun <T: CharSequence?, S> biLet(lhs: T, rhs: T, block: (T, T) -> S): S? =
    if (lhs.isNotNullOrBlank() && rhs.isNotNullOrBlank()) block(lhs, rhs) else null
Francesc
  • 25,014
  • 10
  • 66
  • 84
  • But if the check was only for `null` I could have used the `let` directly right? – Jim Oct 30 '19 at 20:32
  • You would still have to chain 2 lets, with biLet it's a single call. – Francesc Oct 30 '19 at 20:34
  • But in the `biLet` implementation because it is generic I can't check for emptyness. So in the `block` I would need to add that. – Jim Oct 30 '19 at 20:36
  • Yes, this is a null safety function. You could create a variant that checks for empty string instead, I'll update the response. – Francesc Oct 30 '19 at 20:44
2

You can use sequenceOf and none:

if (sequenceOf(foo, bar).none { it.isNullOrBlank() }) {
    println("foo and bar both valid strings")
}
IlyaMuravjov
  • 2,352
  • 1
  • 9
  • 27
  • Nice! I was wondering if that could work if `bar` was something other than `String` – Jim Oct 30 '19 at 21:14
  • It will work if `bar` is a subtype of `CharSequence?`. Otherwise, you won't be able to call `isNullOrBlank()` on `it`. – IlyaMuravjov Oct 30 '19 at 21:42
1

Declare somewhere an extension function using lambdas like:

inline fun String.ifNotEmpty(bar: String, function: () -> Unit) {
    if (this.isNotEmpty() && bar.isNotEmpty()) {
        function.invoke()
    }
}

And use it as:

val foo = "foo-value"
val bar = "bar-value"

foo.ifNotEmpty(bar) {
    println("foo and bar both valid strings")
}
Julio Lemus
  • 651
  • 4
  • 8
1

Improving @Francesc answer, I created a nLet version

fun <S> nLet(vararg ts: Any?, block: (Array<out Any?>) -> S): S? = 
    if (ts.none { when (it) { is String -> it.isNullOrEmpty() else -> it == null } }) block(ts) else null

You can use it like that

nLet (1, 2 , 3, "a", "B", true) { ts ->
    ts.forEach { println(it) }
}
Diego Marin Santos
  • 1,923
  • 2
  • 15
  • 29
0

This is what I use:

fun <P1, P2, R> nLet(p1: P1?, p2: P2?, block: (P1, P2) -> R?): R? = 
    p1?.let { p2?.let { block(p1, p2) } }

Usage:

nLet(foo, bar) { f, b -> doStuff(f, b) }

Add more nLet functions with more P's if more arguments are needed.

neu242
  • 15,796
  • 20
  • 79
  • 114
0

You can also use this for an arbitary number of arguments:

fun <P, R> nLet(vararg ts: P?, block: (Array<out P?>) -> R): R? = 
    ts.takeIf { it.none { it == null } }?.let { block(it) }

Usage:

nLet(foo, bar, dog) { (f, b, d) -> doStuff(f, b, d) }

This works, but f, b and d will have nullable types even though they cannot be null.

(There might be a clever way to solve that...)

neu242
  • 15,796
  • 20
  • 79
  • 114