Kotlin and Rx2. How I wasted 5 hours because of wrong brackets

Roman Iatcyna
AndroidPub
Published in
4 min readAug 24, 2017

--

I heard all that anecdotes about typos destroying a spaceship, wrong brackets blowing up atomic plants, but I thought “Hey, I’m Java developer, it won’t let me build a project with wrong brackets or stuff like that“ so I was pretty confident accepting IntelliSense autocompletions and didn’t know troubles for last few years. Until this time, when Kotlin and Rx2 came into play, the one single replacement of () to {} completely changed the behaviour of the app without any warnings in compile-time.
So let me tell you how I wasted about 5 hours to commit only two characters fix.

Let’s start right from the troubled code snippet. It’s almost the same I wrote it initially in the project. Can anyone notice what’s wrong in this snippet?

This is a part of some Presenter whose interactor is supposed to upload documents and then let router navigate somewhere.
Router.goToNextStep() is a Completable which performs some routing action on subscribe, toMainThread() is a shortcut for observeOn(AndroidScheduler.mainThread()).

But for some reason nothing happened after uploading.

When .andThen{} doesn’t work

Let’s skip tons of theories that I had managed to check until I found the actual reason.

The thing was in andThen() operator parameters:

It expects CompletableSource to be passed. And this is CompletableSource:

As you can see this interface can be represented as lambda, since it has only one method.
And this is what you can read in official Kotlin docs:

Note that parentheses in a call can be omitted entirely if the lambda is the only argument to that call.

Which makes the following string possible:

But what this lambda is actually expanded to is:

Where p0 is actually a subscriber waiting for onNext()/onCompleted() calls. So the router.goToNextStep() is never even subscribed to.

So I basically made an empty Completable that invokes this line of code onSubscribe. And it was executed instead of actual Completable router.goToNextStep().

And you can say man, just RTFM. But you’ll be surprised how much questions like “mergeWith() not working” or “concatWith() not working” on SOF are caused by these damned brackets.

And I understand the reasons why this can easily happen to experienced developer using Rx2+Kotlin:

The first problem is that Android Studio’s autocomplete offer you to use {} when the method has only one parameter that can be passed as lambda.

The second problem is the fact that most of Rx2 methods do accept actual lambdas, some of methods take Callable<ObservableSource<T>> instead of ObservableSource, others take Function<T, ObservableSource<R>>.

Observable.defer { Observable.just(1) } — this will work fine.

Or observable.flatMap { Observable.just(1) } — will work as expected too (if you’re ignoring incoming param purposely).

And the third one is the fact that developers are probably got used to Rx1 which always took Observable in its andThen() method, which couldn’t be represented as lambda thus hadn’t been offered to you by IntelliSense.

To sum up: be careful with Kotlin+Rx2 code using lambdas in parameters where parameter type is ObservableSource or CompletableSource or FlowableSource. Because most likely in that case lambda is not what you want.

P.S. When the actual fix to mine problem was replace {} to ():

.andThen(router.goToNextStep())

I actually offer you to use more reliable construction:

.andThen(Completable.defer { router.goToNextStep() } )

In that case calculation of router.goToNextStep(), which can depend on some state, will be postponed to the time of actual subscription. When the first option constructs Completable at the time the outer Completable is constructed.

In my case there were no difference, since STEP is static field and router doesn’t have states, but consider this option too.

--

--