Craig Otis
Craig Otis

Reputation: 32104

Kotlin idiom for working with non-null object and non-blank String representation

I have a nullable property (a Java object) that knows how to convert itself to a String, and if this representation is not empty, I would like to do something with it. In Java this looks like:

MyObject obj = ...
if (obj != null) {
    String representation = obj.toString();
    if (!StringUtils.isBlank(representation)) {
        doSomethingWith(representation);
    }
}

I'm trying to find the most idiomatic way of converting this to Kotlin, and I have:

    with(obj?.toString()) {
        if (!isNullOrBlank()) {
            doSomethingWith(representation)
        }
    }

But it still feels like too much work for such a simple operation. I have this feeling that combining let, when, and with I can slim this down to something a bit shorter.

The steps are:

  1. If the object (A) is not null
  2. If the String representation (B) of object (A) is not blank
  3. Do something with (B)

I tried:

    when(where?.toString()) {
        isNullOrBlank() -> builder.append(this)
    }

But (1) it fails with:

Unresolved reference. None of the following candidates is applicable because of receiver type mismatch: @InlineOnly public inline fun 
 CharSequence?.isNullOrBlank(): Boolean defined in kotlin.text @InlineOnly public inline fun CharSequence?.isNullOrBlank(): Boolean defined in 
 kotlin.text

And even if it got past that, (2) it would want the exhaustive else, which I don't really care to include.

What's the "Kotlin way" here?

Upvotes: 9

Views: 2013

Answers (1)

Jayson Minard
Jayson Minard

Reputation: 86016

You can use the (since Kotlin 1.1) built-in stdlib takeIf() or takeUnless extensions, either works:

obj?.toString().takeUnless { it.isNullOrBlank() }?.let { doSomethingWith(it) }

// or

obj?.toString()?.takeIf { it.isNotBlank() }?.let { doSomethingWith(it) }

// or use a function reference

obj?.toString().takeUnless { it.isNullOrBlank() }?.let(::doSomethingWith)

For executing the action doSomethingWith() on the final value, you can use apply() to work within the context of the current object and the return is the same object, or let() to change the result of the expression, or run() to work within the context of the current object and also change the result of the expression, or also() to execute code while returning the original object.

You can also create your own extension function if you want the naming to be more meaningful, for example nullIfBlank() might be a good name:

obj?.toString().nullIfBlank()?.also { doSomethingWith(it) }

Which is defined as an extension to a nullable String:

fun String?.nullIfBlank(): String? = if (isNullOrBlank()) null else this

If we add one more extension:

fun <R> String.whenNotNullOrBlank(block: (String)->R): R? = this.nullIfBlank()?.let(block)

This allows the code to be simplified to:

obj?.toString()?.whenNotNullOrBlank { doSomethingWith(it) }

// or with a function reference

obj?.toString()?.whenNotNullOrBlank(::doSomethingWith)

You can always write extensions like this to improve readability of your code.

Note: Sometimes I used the ?. null safe accessor and other times not. This is because the predicat/lambdas of some of the functions work with nullable values, and others do not. You can design these either way you want. It's up to you!

For more information on this topic, see: Idiomatic way to deal with nullables

Upvotes: 15

Related Questions