Runnable vs () -> Unit

In Java, whenever you want to wrap some actions and execute them later, we will probably use Runnable implementation. The run() method of the  Runnable interface takes in no parameters and returns nothing. Many methods will take in Runnable as a callback or as a way to execute actions later.

In Kotlin, the more idiomatic way is to use an anonymous function, i.e, a lambda expression. In this case the run() is () -> Unit. If you forgot the syntax, it has the same semantic as { print("Hello world") }

But not for their usages

Kotlin is designed to simplify code writing, they make it simpler by calling methods that take in a SAM (Single Abstract Method). For example, the Android Handler class has post() method, which requires a Runnable, so in Kotlin, you can simply write:

val handler = Handler()
handler.post {
    Log.d(TAG, "action")
}

Suppose now you want to remove the runnable, you may want to call the removeCallbacks() method, like this:

handler.removeCallbacks {
    Log.d(TAG, "action")
}

Well, this is NOT going to work. To remove callbacks, Handler actually compares the references of the Runnable objects, and only the same reference that we passed into the post() method will be removed. In this case, two lambda expressions actually create two different objects. Although their contents are exactly the same, the posted Runnable will still be run.

Take a look at the decompiled bytecode (as in Tools -> Kotlin -> Show Kotlin Bytecode):

Handler handler = new Handler();
handler.post((Runnable)(new Runnable() {
   public final void run() {
      Log.d(TestActivity.this.TAG, "action");
   }
}));
handler.removeCallbacks((Runnable)(new Runnable() {
   public final void run() {
      Log.d(TestActivity.this.TAG, "action");
   }
}));

As we guessed, the two Runnable objects were created, that definitely does not work.

How to improve it?

In Kotlin, we are able to store any function inside a variable. This is true for lambda as well. Therefore, a natural way to improve the code above is:

val lambda = { Log.d(TAG, "action") }
val handler = Handler()
handler.post(lambda)
handler.removeCallbacks(lambda)

Now we store lambda expression as a variable and pass it to both post() and removeCallbacks() method.

However, this is still NOT achieve what we want. One thing we should keep in mind that Runnable is NOT () -> Unit. The IDE warned us about Type mismatch syntax error.

What is the solution?

We will create a Runnable object in this case. Object expression object: Runnable { ... } is fine. But in this case SAM conversion is good enough:

val runnable = Runnable { Log.d(TAG, "action") }
val handler = Handler()
handler.post(runnable)
handler.removeCallbacks(runnable)

We can check decompiled code to be assured:

Runnable runnable = (Runnable)(new Runnable() {
   public final void run() {
      Log.d(TestActivity.this.TAG, "action");
   }
});
Handler handler = new Handler();
handler.post(runnable);
handler.removeCallbacks(runnable);

Problem solved 😀

So please take extra attention when you want to enjoy smooth development brought by Kotlin. Sometimes it is not free. As the compiler to tell you what it gives, you can always inspect it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s