Kotlin Puzzler: Whose Line Is It Anyways?

Here's a small Kotlin puzzler. What's wrong with the following code (when used on Android)?

val map = mapOf("hello" to "goodbye")  
map.forEach { t, u -> Log.i("tag", t + u) }  

I'll give you a hint: on older versions of Android, the above code crashes due to java.lang.NoClassDefFoundError.

Here's another hint: it has to do with destructuring declarations (or a lack thereof).

Answer

The issue is that the code is calling Java 8's Map.forEach() instead of Kotlin's Map.forEach().

This happens due to a mixup of lambda signatures. The Java 8 Map.forEach() uses BiConsumer, which has a signature of (K, V) -> Unit. By contrast, the Kotlin version of Map.forEach() uses the signature (Map.Entry<K, V>) -> Unit. That means you have to destructure if you want two variables in Kotlin.

The Java 8 version of Map.forEach() wasn't added until API 24, so the code will crash on any older version of Android.

The correct Kotlin is only subtly different, adding parentheses so that we are destructuring Map.Entry<K, V>:

val map = mapOf("hello" to "goodbye")  
map.forEach { (t, u) -> Log.i("tag", t + u) }  

Hopefully one day the API version Lint checks will work on Kotlin code, since it would've caught this problem.

comments powered by Disqus