chris blogs

April 2009

14apr2009 · Programming for Android with Scala

Since I recently ordered a HTC G1 smartphone that runs Android, I wanted to be able to code for it as well. But since I’m a bit allergic to Java, I decided to figure out how to use the nice language Scala on it.

This turned out not to be very hard, but it was frustrating enough that I shall explain how I think to best do it. (Please notice that I don’t really know Java, barely know Scala, had no clue about Android, and generally avoid “the Java way”, so please bear with me, and report any mistakes I did.)

In theory, we can just use scalac to compile our .scala files to ordinary JVM .class-files and they would interoperate without problems. In practice, this approach should be avoided, since Android does things a little bit differently: it does not run Java byte code, but Dalvik byte code, and you need to convert the .class files to .dex files to make them run on the phone, a task I call dexing.

So you can try to dex your .class files together with scala-library.jar, and after figuring how to give dx (the dexing tool) more heap space (dx -JXmx512m), it will happily munch that, think a rather long time, and spit out a not-very-compact ~800kb Android .apk application that does nothing yet.

After doing that a dozen times, you run out of coffee. And swap, maybe. Now, don’t try to “pre-dex” the scala libraries, because in a .dex, everything is mangled into one file, and there are no tools to combine these files.

Instead, you should “treeshake” the application such that only the required Scala classes end up in the Android binary. I’ll use ProGuard for this.

To make things short, here is a step-by-step guide:

  1. Create the application skeleton (I use the Android SDK 1.1r1 for OS X):

    % activitycreator --out helloscala com.example.helloscala.HelloScala
    % cd helloscala
    
  2. Download Scala (I used version 2.7.3) and ProGuard (I used version 4.3), and extract them somewhere ($SCALA, $PROGUARD).

  3. Import the tools:

    % mkdir tools
    % cp $SCALA/lib/scala-{compiler,library}.jar tools
    % cp $PROGUARD/lib/proguard.jar tools
    
  4. Modify the Ant file. For your reference, this is the modified build.xml.

    Add inside the compile task:

    <taskdef resource="scala/tools/ant/antlib.xml"
       classpath="tools/scala-compiler.jar:tools/scala-library.jar" />
    <scalac force="changed" deprecation="on"
            srcdir="${srcdir}" includes="**/*.scala"
            destdir="${outdir-classes}">
        <classpath>
             <pathelement location="${android-jar}"/>
            <fileset dir="tools" includes="*.jar"/>
        </classpath>
    </scalac>
    

    Modify the dex task to look like this (“proguard” as dependency, changed fileset)

    <target name="dex" depends="proguard">
        <apply executable="${dx}" failonerror="true" parallel="true">
            <arg value="--dex" />
            <arg value="--output=${intermediate-dex-ospath}" />
            <fileset dir="${outdir}" includes="*.min.jar"/>
        </apply>
    </target>
    

    Add the proguard task:

    <target name="proguard" depends="compile">
      <taskdef resource="proguard/ant/task.properties"
               classpath="tools/proguard.jar" />
      <proguard>
        -injars ${outdir}/classes:tools/scala-library.jar(!META-INF/MANIFEST.MF,!library.properties)
        -outjars ${outdir}/classes.min.jar
        -libraryjars ${android-jar}
        -dontwarn
        -dontoptimize
        -dontobfuscate
        -keep public class * extends android.app.Activity
      </proguard>
    </target>
    
  5. Now, rewrite the .java file in Scala (don’t forget to remove the HelloScala.java file):

    package com.example.helloscala
    
    
    // we need these _root_ because com.android exists,
    // and we are in com.example.helloscala.
    import _root_.android.app.Activity
    import _root_.android.os.Bundle
    
    
    class HelloScala extends Activity {
      override def onCreate(savedInstanceState: Bundle) {
        super.onCreate(savedInstanceState)
        // use a bit of Scala's stdlib, just to show off
        setContentView(List(R.layout.main) first)
      }
    }
    

Done! Now you can build it by running ant.

If the build succeeded, check bin/HelloScala-debug.apk:

% du bin/HelloScala-debug.apk
20  bin/HelloScala-debug.apk

Nice and small (well, a pure-Java app only has 4k, but its a lot better than 800kb unstripped Scala standard library)! You can check which Scala libraries this uses with:

% jar tvf bin/classes.min.jar

Now start up the emulator, and when it has booted, install your application:

% adb install -r bin/HelloScala-debug.apk

Then, you can select it in the emulator, and you should see the default “hello world” screen.

Congratulations, you are running Scala on Android!

NP: Love—Laughing Stock

Copyright © 2004–2013