3

I have an rx chain that calls an API through Retrofit. I subscribe to my API service, with standard rx subscribe({...}) method and pass a lambda to it. Unfortunately when my call is finally completed, all the code I have added to be executed inside lambda is totally ignored. AndroidStudio suggested a fix which basically adds an inline function run to my lamda and... it magically works. I have no idea what's happening. Why does it not work without run? What does run do?

The code follows:

valuesServiceApi.getValues()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({ data ->
                run { //<- What's this?
                    val cs = data.creditReportInfo.score
                    view.setCreditScore(cs)
                    Logger.getLogger("success:").info("credit score $cs")
                }

            })
shadox
  • 3,238
  • 4
  • 24
  • 38

2 Answers2

7

{ expressions } is a short form of { -> expressions }, which is a function literal with zero parameters.

Therefore,

{ data ->
    {
        val cs = data.creditReportInfo.score
        view.setCreditScore(cs)
        Logger.getLogger("success:").info("credit score $cs")
     }
}

is the same as

{ data ->
    { ->
        val cs = data.creditReportInfo.score
        view.setCreditScore(cs)
        Logger.getLogger("success:").info("credit score $cs")
    }
}

which creates a lambda expression, and do nothing with it.

What you want to do is

{ data ->
    { ->
        val cs = data.creditReportInfo.score
        view.setCreditScore(cs)
        Logger.getLogger("success:").info("credit score $cs")
    }()
}

but this does the same as

{ data ->
    val cs = data.creditReportInfo.score
    view.setCreditScore(cs)
    Logger.getLogger("success:").info("credit score $cs")
}

plus additional function creation overhead.

run { ... } is the same as { ... }() minus additional temporary function creation overhead. So the above is the same as

{ data ->
    run { ->
        val cs = data.creditReportInfo.score
        view.setCreditScore(cs)
        Logger.getLogger("success:").info("credit score $cs")
    }
}
Naetmul
  • 14,544
  • 8
  • 57
  • 81
  • 1
    Switching some of the mentality from Java to Kotlin is hard. So I was basically creating overhead. – shadox Mar 11 '18 at 02:51
  • `run { ... }` is NOT the same as `{ ... }()`. The latter is creating an anonymous inner class, and then executing the function, while `run{ ... }` is inlined and has zero overhead at runtime. – spierce7 Mar 11 '18 at 03:32
2

Look at the definition of the run function. It is a simple inline function that basically does almost nothing. I use it to separate my logic in Kotlin.

val runResult = run { // 
  // Do something in here that doesn't impact the rest of my algorithm / code
  val a = 1
  val b = 2
  a + b // return a + b
}

// Can't access a or b here. run successfully keeps the rest of my algorithm separate / clean

You can also use the run method to create a property inline:

class Example {
  /**
   * Will have the value "0123456789"
   */
  val exampleString: String = run { //
    val sb = StringBuilder()
    for (i in 0..9) {
      sb.append(i)
    }
    sb.toString()
  }
}

Now, that being said, run definitely doesn't impact your RxJava code at all. I recommend you clean your project and run it again. Also, kotlin offers a nicer syntax for functions that have a lambda for the last parameter. If I were writing this, I'd write the following code with the equivalent syntax of what you wrote:

valuesServiceApi.getValues()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe { data ->
            val cs = data.creditReportInfo.score
            view.setCreditScore(cs)
            Logger.getLogger("success:").info("credit score $cs")
        }
spierce7
  • 14,797
  • 13
  • 65
  • 106
  • Hmm, wouldn't ```val exampleString: String = StringBuilder().apply { (0..9).forEach { append(it) }}.toString()``` be a bit cleaner? – Scre Mar 16 '22 at 08:11