4

Is there a way to chain optionally a method in Kotlin, like for example on a FuelManager class, where I would like to have body method as an optional thing, so this would be a request with a body method:

val (_, response, data) = manager .request(Method.POST, url) .timeoutRead(2_000) .timeout(1_000) .header("Content-Type" to "application/json") .header("X-API-Key" to ctx.conf.get(ConfValues.ApiSecret)) .body(payload.toString()) .responseString() 

So, here I would like to check if payload exists, then I would add body method, if not I would not add body to a request. Request without body method.

val (_, response, data) = manager .request(Method.POST, url) .timeoutRead(2_000) .timeout(1_000) .header("Content-Type" to "application/json") .header("X-API-Key" to ctx.conf.get(ConfValues.ApiSecret)) .responseString() 
1
  • 3
    See this. Consider using apply instead of chaining. This way you can put if statements wherever you like. Is each of the chained methods declared in the same type? Commented Sep 17, 2021 at 12:20

5 Answers 5

8

I agree with Sweepers comment, that apply may be nicer to use here, e.g.:

val (_, response, data) = manager.apply { request(Method.POST, url) timeoutRead(2_000) timeout(1_000) header("Content-Type" to "application/json") header("X-API-Key" to ctx.conf.get(ConfValues.ApiSecret)) if (!payload.isNullOrBlank()) body(payload.toString()) }.responseString() 

Inserting an if-statement is relatively straight-forward in that case.

If you can't trust that what you have follows fluent API best practice (i.e. returning the actual instance again), you may instead use also something as follows:

val (_, response, data) = manager .request(Method.POST, url) .timeoutRead(2_000) .timeout(1_000) .header("Content-Type" to "application/json") .header("X-API-Key" to ctx.conf.get(ConfValues.ApiSecret)) .let { if (!payload.isNullOrBlank()) it.body(payload.toString()) else it } .responseString() 

I.e. putting the if-statement inside the scope functions let or run.

Sign up to request clarification or add additional context in comments.

1 Comment

Drat, you beat me to it by 3 mins :-) Yes, let() is the most direct solution here — though, as others have said, these long chains are hard to read and maintain, so there are better approaches.
5

I think this should do the job

val (_, response, data) = manager .request(Method.POST, url) .timeoutRead(2_000) .timeout(1_000) .header("Content-Type" to "application/json") .header("X-API-Key" to ctx.conf.get(ConfValues.ApiSecret)) .apply { if(!payload?.toString().isNullOrBlank()) { body(payload.toString()) } } .responseString() 

See https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/apply.html

Comments

2

Like people have said, long chains can be hard to read (especially if you start adding a bunch of logic into them), so it might be better to break it down into some well-named functions instead

val (_, response, data) = manager.apply { request(Method.POST, url) timeoutRead(2_000) timeout(1_000) header("Content-Type" to "application/json") header("X-API-Key" to ctx.conf.get(ConfValues.ApiSecret)) addBodyIfRequired(payload) }.responseString() private fun WhateverManager.addBodyIfRequired(payload: Payload) = apply { if (!payload.isNullOrBlank()) body(payload.toString()) } 

That way it reads a little clearer, and if you ever need to tweak the logic it can happen in that separate function, and your builder still looks nice and clean. You tend to see this kind of thing in functional programming, where you can have long pipelines of operations, but you create small functions you can chain together in a neat and readable way

Plus with extension functions, you can kinda rewrite how the builder looks - say if you wanted to combine the two timeout calls into one, with named parameters. It's definitely a personal preference thing, but it can really help when there's a lot happening and you want to keep it concise!

Comments

2

If you tend to often write code like:

something .let { if (condition1) block1(it) else it } .let { if (condition2) block2(it) else it } 

I suggest adding these custom scope extension functions to your arsenal:

inline fun <T> T.letIf(condition: Boolean, block: (T) -> T): T = if (condition) this.let(block) else this inline fun <T> T.runIf(condition: Boolean, block: T.() -> T): T = if (condition) this.run(block) else this inline fun <T> T.alsoIf(condition: Boolean, block: (T) -> Unit): T = if (condition) this.also(block) else this inline fun <T> T.applyIf(condition: Boolean, block: T.() -> Unit): T = if (condition) this.apply(block) else this 

Then you could use them like this:

import my.cool.extensions.letIf something .letIf (condition1) { block1(it) } .letIf (condition2) { block2(it) } 

Comments

0

You can use isNullOrBlank() "Returns true if this nullable char sequence is either null or empty or consists solely of whitespace characters."

2 Comments

How can I use that for my case where I would not add body method call if payload doesn't exists?
with the apply { if } from the answer by lambda dude

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.