The Android platform provides a set of well designed, object-oriented APIs.
These interfaces are written in Java, for Java.
So: Why use another language?
Let’s say I want to run some code.
Like in this DialogInterface.OnShowListener
:
Interface used to allow the creator of a dialog to run some code when the dialog is shown.
Here we go!
dialog.setOnShowListener(
new DialogInterface.OnShowListener() {
public void onShow(DialogInterface interface) {
runSomeCode();
}
}
);
I also need to run some code in a background thread:
new AsyncTask<MyObject, Integer, Integer> () {
protected Integer doInBackground(MyObject... objs) {
runSomeCode(objs[0]);
}
}.execute(myObject);
And post code back to the UI thread:
handler.post(new Runnable() {
public void run() {
runSomeUICode();
}
});
Oh, geez. Can’t I just—
RUN SOME CODE?
dialog.setOnShowListener { di: DialogInterface =>
runSomeCode()
}
future { runSomeCode(myObject) }
post { runSomeUICode() }
Not in Java. :(
Scala can stand in for Java in almost any situation.
Including Android programming.
Before you sneak code into a virtual machine, you can do whatever you want.
You want closures, mix-ins, even generic type reification?
Scala’s compiler is an eager conspirator.
But once the runtime show starts, Scala won’t pull anything fancy.
Live compilation, or open classes?
That would be a dead giveaway.
Those tricks may blur away on desktop hardware.
But on a mobile device, such artifice revealed in slow motion.
Scala’s advanced techniques compile to JVM primitives that are executed normally.
So that neither the JVM, nor Android’s Dalvik VM
—or more importantly their users—
know the difference.
Scala blends in to Android like a Russian spy at Microsoft.
Traits let you mix behavior into base Android classes.
trait ScalaActivity extends Activity {
Like a direct path to the UI thread:
lazy val handler = new Handler
def post(block: => Unit) {
handler.post(new Runnable{
def run { block }
})
}
To take care of business in one line.
post { dlg.dismiss() }
Implicit conversions extend APIs statically.
implicit def f2cancel(block: DialogInterface => Unit) =
new DialogInterface.OnCancelListener {
def onCancel(dialog: DialogInterface) {
block(dialog)
}
}
So your code doesn’t have to.
new AlertDialog.Builder(this)
.setOnCancelListener {
di: DialogInterface => finish()
}
We can even add “automatic resource block management.”
def editor(
block: SharedPreferences.Editor => Unit
) = {
val editor = sp.edit()
block(editor)
editor.commit()
}
And commit behind the scenes.
sp.editor { e =>
e.putString("oauth_token", token.value)
e.putString("oauth_token_secret", token.secret)
}
Plus, you can use your favorite Scala libraries!
future {
http(some_url >> { stm =>
val bitmap = BitmapFactory.decodeStream(stm)
post { some_image.setImageBitmap(bitmap) }
} )
}
So what’s the catch?
Scala isn’t just statically typed like Java.
It’s statically typed better than Java.
And because Scala’s type system is more powerful than Java’s,
expectations for type safety in application code are higher.
Scala makes us picky.
Things start off all right.
<Button id="@+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/my_button_text"/>
But then—ew.
Button myButton =
(Button) findViewById(R.id.my_button);
And then—no.
val myButton =
findViewById(R.id.my_button).asInstanceOf[Button]
Why does it return just a View
?
We declared the subtype in the layout.
But aapt
dumbed it down to this:
public final class R {
...
public static final class id {
public static final int my_button=0x7f060003;
By the time our code is compiled, the crucial type information has been discarded.
It can’t be restored by any coding trickery.
If only there were some way to add our own step to the build process.
This sbt plugin works with Android SDK tools to build applications in a controlled, predictable, and flexible process.
And you can hack it.
You can add a pre-compilation step to generate a file TR.scala
that is like R.java
, but with types.
Lookup this blog post if you want to know how it works ↓
To just use it, you need to have Simple Build Tool installed.
Then you declare the plugin dependency in your sbt project,
and mix a TypedResources
trait into the project definition.
Perhaps an example would help?
Meetabout turns web sites into meetups.
(Or, it will when it’s finished.)
Let’s clone it from github:
$ git clone git://github.com/meetup/meetabout.git
And start sbt:
$ sbt
It’s downloading the plugin! Why?
Because in project/plugins/Plugins.scala
, we
have this:
import sbt._
class Plugins(info: ProjectInfo)
extends PluginDefinition(info)
{
val android = "org.scala-tools.sbt" %
"sbt-android-plugin" % "0.5.0"
}
Now we’re in the sbt console. We need to update the application’s dependencies first:
> update
And then compile:
> compile
Uh oh!
You need to set ANDROID_SDK_HOME or ANDROID_SDK_ROOT
> ^D
$ export ANDROID_SDK_HOME=/usr/local/android-sdk/
$ sbt
Now it will work.
This project has been enhanced with our obsessive-compulsive-typing trait:
class MeetaboutProject(info: ProjectInfo)
extends AndroidProject(info: ProjectInfo)
with MarketPublish with TypedResources {
So it generates an extra source before compilation, TR.scala
.
Now in app code, we can do this cool stuff:
class SomeActivity extends Activity with TypedActivity {
val name = findView(TR.namefield).getText
Build and put into the emulator:
> install-emulator
Load up a sweet web site.
Share it with some installed apps.
Somebody’s meeting up about this page!
Meetabout queried the Meetup Everywhere API to find content tagged with that URL.
Now we just need to find or create one one near us, sign up for it, etc...
It’s going to take a little while to build all this into the app.
But this is the general idea!
Forks welcome.
Scala’s structural similarities to Java allow it to work as normal on Android.
Scala language constructs can be implicitly added to Java APIs that don’t know anything about them.
Simple Build Tool can build whatever crazy idea you come up with.
So get to work.