A little Kotlin puzzler

25 Sep 2018

A little Kotlin puzzler

2 minute read

Kotlin is an incredibly enjoyable, concise and powerful programming language. Yet sometimes also a bit confusing…

Puzzle

Have a look at the simple class below. It simulates an ongoing operation by smoothly moving a progress bar from 0 to 100 over the course of 30 seconds:

class ProgressbarAnimator(private val progressBar: ProgressBar) : AnimatorUpdateListener {

    private lateinit var animator: ValueAnimator

    init {
        configureAnimator()
        animator.addUpdateListener { this }
        animator.start()
    }

    fun configureAnimator() {
        val endValue = (FPS * DURATION).toInt()
        progressBar.max = endValue

        animator = ValueAnimator.ofInt(0, endValue)
        animator.duration = DURATION
        animator.interpolator = LinearInterpolator()
    }

    override fun onAnimationUpdate(p: ValueAnimator?) {
        progressBar.progress = animator.animatedValue as Int
    }

    companion object {
        private const val FPS = 0.06
        private const val DURATION = 30 * 1000L
    }
}

What do you think will happen if we instantiate one of these with a given progress bar?

Well… nothing! The progress bar doesn’t move at all.

Can you spot what’s wrong? The error is in the following lines:

init {
    configureAnimator()
    animator.addUpdateListener { this }
    animator.start()
}

Any luck?

Well this is the actual culprit:

animator.addUpdateListener { this }

There is syntactically a very subtle, yet incredibly important difference between that line and this:

animator.addUpdateListener(this)

If we change our init to the latter, then the progress bar works as expected!

Explanation

One of the Kotlin features is that if the last argument of a method call is a lambda, you can move the lambda outside of the method invocation. (which is great for building DSLs)

Consequently,

animator.addUpdateListener { this }

is equivalent to

animator.addUpdateListener() { this }

and even

animator.addUpdateListener({ this })

and by expanding the lambda, this

animator.addUpdateListener({ _ -> this })

So the reason why the progress bar wasn’t working is simple. Instead of registering itself as an AnimationUpdateListener, it actually registered a lambda, a new function to handle the animation updates.

So every animation update the lambda { _ -> this } was invoked instead of the onAnimationUpdate method. Thereby not doing anything, it just has the this object without any invocation on it.

Equivalent to writing the following function:

fun doNothing() {
  5
}

Perfectly valid syntax, but otherwise completely useless.

Wrap-up

A very subtle difference in syntax ({} instead of ()) can make a huge difference in what the code actually does. Kotlin is a very powerful programming language, but with great power comes great…

If you’ve made it this far you should probably follow me on Mastodon. Feel free leave a comment below!

Leave a Comment

Start a conversation about this content on Reddit or Hacker News.