Scala on Android Gotcha: HelloGridView Error
Short Version
If you are working on the HelloGridView example in scala, you'll quickly run into a problem. Assuming you've translated the java properly, scalac will fail to compile citing this line in the ImageAdapter:
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
Change this to:
imageView.setLayoutParams(new AbsListView.LayoutParams(85, 85));
That's android.widget.AbsListView.
Long Version
I just got the Droid Incredible, and it is freaking awesome. I was excited to start hacking on some android code over the weekend, in scala. The first thing I did was shop around for a sane toolchain. I'm a fan of sbt, so I wanted a simple way to write android code without an IDE telling me how things need to be done. This is totally possible, thanks to the design of the SDK, and if you are looking to pair sbt with your editor of choice (jedit here), I suggest you read through this excellent post on getting started. Total cake. I also must direct you to a more comprehensive presentation by Nathan Hamblen, which goes into detail about various challenges, considerations and gotchas you should be aware of when writing scala for android. Now, back to the problem of GridView.LayoutParams.
The problem is that there is no such type. Let's look at ImageView, a subclass of View by which it inherits setLayoutParams. This method wants an instance of ViewGroup.LayoutParams, a static inner class that is sometimes overridden to implement view-specific mangling. If you poke through the API, you'll find several parings of layouts and their layout-specific subclasses, like LinearLayout and LinearLayout.LayoutParams. GridView has no such companion subclass, even though the example references it in the source, which does in fact compile in java and function correctly.
How can this be? GridView is a ViewGroup, which defines the static inner class LayoutParams. In java, it is perfectly legal to reference a static member defined in a super class by way of its sub class. Scala has no statics, and instead deals with static java members as members of a singleton or companion instance. This is fine, except that it means we cannot refer to GridView.LayoutParams as we could in java, because GridView defines no such class.
My first attempt at a workaround was to simply use a raw instance of ViewGroup.LayoutParams instead:
imageView.setLayoutParams(new ViewGroup.LayoutParams(85, 85));
It compiles. Sweet! Success! Next I sbt reinstall-emulator (install the compiled apk into a running emulator via sbt), fire up the app, and BOOM - runtime error. Well crap. What happened? Type-unsafety happened. Use adb logcat to peep the logs, and you'll find a nice little ClassCastException coming from GridView. Ok, let's look at the GridView source. I'm running against an older version of the android API, but line 936 is the culprit:
AbsListView.LayoutParams p =
(AbsListView.LayoutParams)child.getLayoutParams();
That's an explicit cast. Yuck. Look again at the hierarchy and you'll see that AbsListView.LayoutParams is, naturally, a sub class of ViewGroup.LayoutParams, which in turn means that casting ViewGroup.LayoutParams to AbsListView.LayoutParams is impossible. However, now that you've looked under the hood you know that GridView expects its children to express their layout parameters as instances of AbsListView.LayoutParams. Sure enough, switching that in solves the runtime problem, but it introduces a new one. The android API may, for some reason, require a change to GridView such that AbsListView.LayoutParams is traded for something else (like a real GridView.LayoutParams type). Arg! That means code breakage!
What do we do about this problem which, at its core, is an issue of scala/java interop? Here are 3 options:
- Code to the type we know the API is using, like we did above. This works, but renders us quite vulnerable to changes that upstream vendors (like the android devs) make.
- Use java. In cases like this, you could argue that it makes more sense to use java, especially given that several tools handle mixed-mode projects quite well (maven included, although though I've experienced mixed-mode issues at its hands, while sbt has been solid).
- Abstract this pain behind something that will vend the required instances. This might mean a function that yields AbsListView.LayoutParam instances, but provides you safety in its centralization. If the android API changes, you make one change.
My preference is number 3. I'm not a masochist, so that rules out 1 (and perhaps 2). Also, if I wanted to write java for android, I'd write java, not scala. 3 makes perfect sense. Abstractions are, after all, a balm to a developer's wounds.
