5 notes

Rewiring Android for type-safe layout resources

This is the backstory for Rewiring Android with Scala. If you’re at OSCON this week, don’t miss the frontstory on Tuesday at 2:15.

Android interface layouts are defined in human readable resource files—thank God. These could be generated and edited by visual layout tool, and in fact there is one upstart that does just that, roughly. In time there will be many editors competing to produce this format the most smoothly.

These XML layout definitions are refreshingly open about their relationship with executable code. Element names correspond to class names, fully qualified or assumed to live in the default package of android.widget. In a widget’s onCreate callback, you associate it with a layout with a unique identifier using a reference such as R.layout.shareactivity; this reference is defined in a file R.java that the aapt tool generates.

On this JVM-esqe platform, it would be hard to substantially improve on this process. A less ambitious approach would have skipped the reference generation process and asked applications to refer to layouts by name, introducing the possibility of spelling errors. Android’s approach is foolproof with one caveat: the resource values are all integers, so you could pass a string reference (R.string.app_name) to a method that expects a layout reference and this would be bad news at runtime. Fortunately, the static inner classes layout and string string make this an unlikely error. (Yes, Google named a class without an initial capital to make the reference look nicer in context. This is why they are Google, and not Sun—or Oracle.)

It’s the way programmatic access to a particular layout element is accomplished that will make a typeophile reach for the shotgun:

Button createButton = (Button) findViewById(R.id.createbutton);

Maybe that’s okay in Java, but this is not the kind of code you upgrade to Scala to write:

val createbutton = findViewById(R.id.createbutton).instanceOf[Button]

It is the rare occasion where Scala source is longer than the equivalent Java. Intentionally in Scala, casting is as ugly in code as it is in concept. And that’s not all there is to be peeved about here: having to import Button when you aren’t constructing with or otherwise using the type name is just annoying. It’s exactly the kind of trivial but legitimate grievance that drives a person to drink—or to use dynamic typing, same thing.

There are of course lame workarounds to this. We could defy Odersky’s wisdom and make our type assertions prettier by defining a utility method somewhere:

val createButton = findView[Button](R.id.createbutton)

Still we have to import Button, or use its full name. Or define separate utility methods for each type of view we need to find:

val createButton = findButton(R.id.createbutton)

But that obviously stinks.

There just isn’t much you can do by working off the untyped resource reference generated by the Android SDK, and you can’t really fault Google for it either. Casting in application code is practically idiomatic for Java; it’s certainly a lot more common than using generic classes for something that isn’t a collection.

Anyway, here’s that shotgun:

import sbt._
import scala.xml._

trait TypedResources extends AndroidProject {
  ...

What’s this? It’s a project mix-in included with version 0.5.0 of the sbt-android-plugin! Remember how the element names in the layout definition are easily resolvable to classnames? We can use that to generate our own typed resource references on top of the untyped ones produced by aapt.

If this sounds like difficult or unpleasant work, you are probably used to a crappy language and/or build tool. Because it’s actually really easy to build up a map of resource identifiers to classnames:

And then, whatever, you write that data out as a Scala source file. This all happens in a task that we can hook into the standard build sequence:

override def compileAction = super.compileAction dependsOn generateTypedResources
lazy val generateTypedResources = fileTask(typedResource from layoutResources)   
{ ... }

The generated file contains typed resources that look like this:

case class TypedResource[T](id: Int)
object TR {
  val urlfield = TypedResource[android.widget.EditText](R.id.urlfield)
  val searchbutton = TypedResource[android.widget.Button](R.id.searchbutton)
  val namefield = TypedResource[android.widget.EditText](R.id.namefield)
  val createbutton = TypedResource[android.widget.Button](R.id.createbutton)
}

Finally, our type has been reunited with its identifier. To make it useful, the generated source also includes some access interfaces:

trait TypedViewHolder {
  def view: View
  def findView[T](tr: TypedResource[T]) = view.findViewById(tr.id).asInstanceOf[T]  
}
trait TypedActivity extends Activity with TypedActivityHolder { def activity = this }
trait TypedView extends View with TypedViewHolder { def view = this }

There are also implicit conversions for activities and views that you don’t control, but this explicit case is the most common:

class MyActivity extends Activity with TypedActivity {
  lazy val createButton = findView(TR.createbutton)
  ...

So it’s still casting—there’s no getting away from that—but it has eliminated redundant code and reduced the potential for runtime errors. If a layout includes a custom widget but specifies an erroneous classname, the generated TR.scala will fail to compile. If you define layout resources of different types with the same identifier, the plugin will issue a warning. You can still mess up despite all this, but it’s harder.

Just for kicks, here what application code in Java would look like if a similar type infrastructure were in place:

Button searchButton = findView(TR.searchButton)

That’s right, no hideous angle brackets required! Even ol’ javac can infer the type parameter of a method from its value parameters, thankyouverymuch. TypedResource would be a class with a generic type T and a static final id field initialized in its constructor. Who needs those fancy case classes?

Google should really implement this in the platform itself, so that Java applications can benefit too. Until then, enjoy your TR.scala.



1 note
“The word ‘multitouch’ gets thrown around quite a bit and it’s not always clear what people are referring to. For some it’s about hardware capability, for others it refers to specific gesture support in software. Whatever you decide to call it, today we’re going to look at how to make your apps and views behave nicely with multiple fingers on the screen.”

Making Sense of Multitouch

}