I decided to have a go at porting my other Android Wear app to Kotlin. This one is a stopwatch / countdown timer (Github link, Play Store link). This experience went a bit faster than the last time, since I had a much better idea what’s going on, but it also turned up a few more gotchas.
FWIW, this was all done with the latest Android Studio 2.0 Preview 9 with the latest plugins and such.
Fun with the Java to Kotlin converter: A few weird things happened this time. The translation of get/set methods made a critical error on a setter method, generating something like this:
var isVisible: Boolean = false
set(visible) {
Log.v(TAG, "visible: " + visible)
this.isVisible = visible
...
}
The proper code that it should have generated is field = visible
. The generated code instead recurses infinitely. Oops. Also, I again had to spend a fair bit of time dealing with declaring Android callback types as possibly null. Some future world with annotations that catch this sort of thing would be highly desirable.
Callbacks from an XML layout: This app has a bunch of XML files that specify widget layouts, and uses Android’s reflective ability to call public methods. The raw XML looking something like this:
<ImageButton
android:id="@+id/timerButton"
android:onClick="launchTimer"
... />
In this case, launchTimer
is a method on the Activity, as you’d expect: fun launchTimer(view: View) = { ... }
. Everything runs correctly, but Android Studio highlights launchTimer
in the XML with “Method ‘launchTimer’ is missing in ‘StopWatch’ activity or has an incorrect signature”. Similarly, in the activity, the method in question is greyed out and noted as not being called. The Kotlin/Android integration isn’t yet connecting this particular call edge correctly as it does with Java.
Limitations in kotlinx.android.synthetic: For Android Wear apps, it’s a standard practice to have two layouts – one for round watches and one for square. You can have widgets with all the same names, and the helper library will instantiate whichever layout is appropriate for the device. The traditional findViewById(R.id.whatever)
code you might write then doesn’t have to worry about it, since you’ve got the same IDs in each layout file. So far as I can tell, the synthetic stuff can tell the difference, and insists on importing either the “round” or “square” layout rather than letting you have a “generic” layout. For now, anyway, I’m just doing things the more verbose way.