2

I am new to Kotlin. Can someone tell me how can I set the value only if not null here in below cade. There should be a way to use with let() but I am not sure how to do it.

If the var2 is not null only I should set it. Otherwise null pointer error will throw.

private fun myFunc(var1: Type1 , var2: Type2?) {
    
        val request = class1.newBuilder()
            .setType1(var1)
            .setType2(var2) // how to set var2 only if not null? 
            .build()
        clientClass.send(request)
 }
ever alian
  • 1,028
  • 3
  • 15
  • 45

2 Answers2

5

If each builder function returns a new Builder instance, use run:

private fun myFunc(var1: Type1 , var2: Type2?) {
    val request = class1.newBuilder()
        .setType1(var1)
        .run { if(var2 != null) setType2(var2) else this }
        .build()
    clientClass.send(request)
}

If the builder functions mutate and return the same Builder instance, it’s simpler to use apply

private fun myFunc(var1: Type1 , var2: Type2?) {
    val request = class1.newBuilder()
        .setType1(var1)
        .apply { if(var2 != null) setType2(var2) }
        .build()
    clientClass.send(request)
}

// or more cleanly using apply for everything instead of chaining:

private fun myFunc(var1: Type1 , var2: Type2?) {
    val request = class1.newBuilder().apply {
        setType1(var1)
        if(var2 != null) setType2(var2)
        build()
    }
    clientClass.send(request)
}

Example of a Builder class whose functions return new instances:

fun setType2(type2: Type2): Builder {
    return CombinedBuilder(this, type2) // a new object
}

Example of a Builder class whose functions return the same instance:

fun setType2(type2: Type2): Builder {
    this.type2 = type2
    return this // the same object
}

The second type is more common, but sometimes the first type is used. You might have to check the source code to know for sure. If you can't be sure, use the .run method because it will work for either.

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • can you explain the difference in above 2? what do you mean by new builder instance or same builder instance? – ever alian Jun 09 '22 at 13:57
  • I added additional info to explain. – Tenfour04 Jun 09 '22 at 14:08
  • 1
    One other thing...`let` and `also` could be used instead of `run` and `apply` respectively, but I think in this case the semantics of the Builder being `this` makes more sense. You're not passing the builder to something else, but rather calling something on it. – Tenfour04 Jun 09 '22 at 14:21
  • 2
    If the functions are mutating the same Builder, it's probably cleaner to wrap the whole lot in `apply` I reckon - then you get the nice familiar "do this, if X then do this" flow instead of having to chain things – cactustictacs Jun 09 '22 at 14:25
  • can you update the code with ´let´ and ´also´? – ever alian Jun 09 '22 at 14:26
  • 1
    @everalian It's about the internals of a Builder pattern. To do call chains (like `builder.setType1(var1).setType2(var2)`), methods `setType1` and `setType2` have to return something of type Builder. That could be the same instance created by `newBuilder` (example 2) or a new instance (example 1) – Dmitry Kaznacheev Jun 09 '22 at 14:27
  • 1
    Also, I've never seen a builder that doesn't mutate the same Builder – does anyone know any examples? – Dmitry Kaznacheev Jun 09 '22 at 14:30
  • In my case, it is the second type. Return the same instance. – ever alian Jun 09 '22 at 14:31
  • 1
    @everalian then I'd suggest `.apply { var2?.let { setType2(it) } }` – Dmitry Kaznacheev Jun 09 '22 at 14:32
  • I would like to know why can't use the let and also in this case. Tell me the reason. – ever alian Jun 09 '22 at 14:34
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/245458/discussion-between-dmitry-kaznacheev-and-ever-alian). – Dmitry Kaznacheev Jun 09 '22 at 14:36
  • @everalian You *can use `let` or `also` and it would be functionally equivalent. It's just more semantically appropriate to use `run` and `apply`. It has to do with whether the object you're using the scope function on is doing something to something else or is having something done to it. – Tenfour04 Jun 09 '22 at 14:59
  • @cactustictacs Agree, if you can use `apply` it's cleaner to use it for the whole block. – Tenfour04 Jun 09 '22 at 15:00
  • @DmitryKaznacheev I know I've seen it before, but I can't remember where. Kotlin CoroutineContext does something like this, although it's not exactly the same thing as a typical builder. – Tenfour04 Jun 09 '22 at 15:02
1

Using scope functions is one way to solve this but results in code that is hard to read imo. If you stick to the builder pattern, the simplest solution would be to modify the builder to accept Null values:

class Builder {
    private var type1: Type1 = <default>
    private var type2: Type2 = <defaul>

    fun setType1(type: Type1?): Builder {
        if (type != null) this.type1 = type
        return this
    }
    fun setType2(type: Type2?): Builder {
        if (type != null) this.type2 = type
        return this
    }
}

That way you can keep:

class1.newBuilder()
    .setType1(var1)
    .setType2(var2)
    .build()

To communicate the fact that the values won't be set if they are Null, change the name of the setters to e.g. setTypeIfNotNull.

With Kotlin the builder pattern is kind of obsolete (unless you create a DSL) because you can use a constructor with default and named arguments instead, something like:

class Request(val type1: Type1 =  <default>, val type2: Type2 = <default>)

Then you can do:

Request(
    type1 = type1,
    type2 = type2,
)

Now this doesn't cover the Null case but you can use this to accept null values:

companion object {
    public operator fun invoke(type1: Type1, type2: Type2? = null) = Request(type1, type2 ?: <default value>)
}

The reason why there's a companion object instead of a constructor is explained here: Is there a way to use the default value on a non-optional parameter when null is passed?.

Emanuel Moecklin
  • 28,488
  • 11
  • 69
  • 85
  • Kotlin's scope functions are really handy, and can give quite concise and readable code when used well — it's well worth getting comfortable with them, IMO. In fact, [`apply()`](https://kotlinlang.org/docs/scope-functions.html#apply) was intended for exactly this kind of thing; I find it quite natural. – gidds Jun 09 '22 at 17:15
  • @gidds I'm very comfortable with scope functions but people tend to overuse them when simpler solutions exist. Also there are some pitfalls. E.g. this: https://gist.github.com/1gravity/b5f07563da50762dd60bf0c9e93eea4f. Do you see what's wrong with it? – Emanuel Moecklin Jun 09 '22 at 23:34
  • I think I see, yes… (In fact, `run()` is the scoping function I use least, because its intended usage is less clear than the others, and it leaves you open to that sort of problem.) – gidds Jun 09 '22 at 23:55
  • @gidds it's not a `run` issue, the same would happen if I had used `?.let { processFile(it) }` – Emanuel Moecklin Jun 10 '22 at 02:48
  • Yes, but I think it would have been more obvious. – gidds Jun 10 '22 at 10:07