views:

681

answers:

4

The definitive reference for using Scala on android seems to be here: http://www.scala-lang.org/node/160

Unfortunately, all the references on using scala with android are based around Scala 2.7 and refer to a custom build android-library.jar, with a couple of cryptic references suggesting that this custom build isn't needed for later versions of android (I'm using 2.1 / API v7)

So... What are the steps needed to use Scala 2.8 in an android project? Preferably using eclipse and the Android tools that Google supplies for that IDE.

+8  A: 

My approach:

  • Use the normal Android/eclipse tools for creating a Java project.
  • Add a second project containing the Scala code. That way I get to keep the generated code for future reference (I'm new at both Android and Scala). This project can reference android.jar.
  • The scala project produces a jar file that's used in the java project
  • Use proguard to strip the library. I believe this avoids the need for the scala-android.jar that was used in 2.7

I haven't used this for anything more ambitious than hello, world though, so take it as more of a set of hints.

In the scala project, I add a builder (Builder > New) that's just a shell script called pguard in the root directory of the project containing:

#!/bin/sh
cd $1
PROGUARD=$HOME/dev/proguard/lib/proguard.jar
LIBS=
OUTPUT=lib/proguard.jar
rm -f $OUTPUT
AJAR=/Users/jamesmoore/dev/android-sdk-mac_86/platforms/android-7/android.jar
# java -jar $PROGUARD -injars 'bin:lib/scala-library.jar(!META-INF/MANIFEST.MF,!library.properties)' -outjar $OUTPUT -libraryjars  @proguard.txt
java -Xmx1g -jar $PROGUARD -injars 'bin:lib/scala-library.jar(!META-INF/MANIFEST.MF,!library.properties)' -outjar $OUTPUT -libraryjars $AJAR @proguard.txt

The builder has Location set to:

${build_project}/pguard

And both working directory and arguments set to

${build_project}

Also in the root of the scala project, there's a proguard arguments file @proguard.txt:

-dontwarn
-dontoptimize
-dontobfuscate
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
                SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
-keep public class com.banshee.** {
    public protected *;
}

You'll want to change the -keep arguments to keep your own code, of course.

In the java project, I add the jar file that's produced by the scala project (I use lib/proguard.jar in the script above).

Don't add the scala project as a required project in the java project's build path, though. That will add the scala class files in addition to the jar file and confuse dex (since it'll get both the .class files and the same things in the jar). As far as I can tell, Eclipse will build everything in the workspace, so when you hit the go button, both projects get built.

James Moore
Interesting... if this also works with android.jar in the "Scala" project then you have yourself an accepted answer :)
Kevin Wright
Yes, it should work - my config does use android.jar in the scala project. The "-libraryjars $AJAR" bit in the code above tells proguard to look at android.jar but not to include any android code in the jar; that'll be handled by the main project.
James Moore
Not a bad solution, but it stopped me from using resources effectively. I've found a way using ant though...
Kevin Wright
I'm accepting because it's a good, valid answer, plus it has a certain elegance. It's not my ultimate solution but I don't see that as a valid excuse to deny you the points :)
Kevin Wright
+2  A: 

After much investigation, it really does look like Proguard is essential to keep the size and speed of deploying the application to reasonable levels.

Sadly, there is no suitable way to embed proguard as a build step. Using scripts might be a possibility, but I also need to support Windows, Linux and OSX environments.

I was also unsure about the twin-project solution, as it prevented Scala code from using the generated resources file R.java, which I wanted to be able to do.

In the end, I was able to make both SBT and Ant build an android 2.1 application using Scala 2.8. Ant was the favourite final solution as it works with the same file organisation as Android's eclipse plugin. I've written up the solution here: http://scala-ide.assembla.com/wiki/show/ae55a-oWSr36hpeJe5avMc/Developing_for_Android

Eclipse then launches Ant as an external tool to package and install the application.

Kevin Wright
I think both of those (no R access; Windows) are solvable problems.For R (and anything else you want to write in Java): In the scala project, you can just add the Java project bin directory to the Scala project (open the project properties, go to Java Build Path, add YourJavaProject/bin).For the proguard build phase: it doesn't have to be a script. I'm running on OSX and Windows right now; just move everything into the @proguard file.
James Moore
Sounds like it could work, but it also introduces a circular dependency between the two projects. That shouldn't be an issue in this case but it still makes me nervous
Kevin Wright
+2  A: 

I'm now using a modification of my previous answer to run on Windows: just move everything into @proguard_windows.txt so you don't have to worry about running as a script.

My new @proguard_windows.txt looks like:

-injars bin;lib/scala-library.jar(!META-INF/MANIFEST.MF,!library.properties)

-outjar gen/scandroid.jar

-libraryjars lib/android.jar

-dontwarn
-dontoptimize
-dontobfuscate
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers

-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
                SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
-keep public class com.banshee.** { public protected *; }
-keep public class org.xml.sax.EntityResolver { public protected *; }

And note that in windows, you need to use a semicolon for -injars. Nasty.

The builder looks like this:

(running cygwin here, so the cat option path takes a slash)
James@Greine:/cygdrive/c/Users/james/workspace/Scala2$ cat .externalToolBuilders/proguard.launch
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="C:\Windows\System32\java.exe"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="-Xmx1g -jar lib/proguard.jar @proguard_windows.txt"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${build_project}"/>
</launchConfiguration>

You'll want to put this in .externalToolBuilders/proguard.launch.

The interesting thing here is that it's just a java command, not any kind of shell script, so it's fairly easy to port between Windows/Mac (and I'm assuming Linux, but haven't done that yet), since you're just changing the location of the actual java binary.

(Submitting this as a new answer because it's a bit different than the one that got marked as the correct(ish) answer)

James Moore
Success! FYI, you need to add android.jar from the android sdk, and the entire contents of scala's lib directory (well, maybe not the entire contents, but more than just android-library.jar) to the scala lib project directory for proguard to work correctly. Then add your android project's bin directory to your scala project's classpath, and gen\scandroid.jar from your scala project to your android project's class path. Then you may have to hit build a couple of times to get the cyclical dependency resolved (it's not a true cyclic dependency since the R class doesn't depend on the scala proj.
Jeremy Bell
clarification: The cyclic dependency being mentioned is: Android project's gen folder depends on nothing -> Scala project depends on android project's gen classes -> Android project depends on scala project's scandroid.jar. Ideally, this would just work in one project (why doesn't it?).
Jeremy Bell
I almost got the one-project proguard setup to work, but I had an issue with proguard including the R class in scandroid.jar. If you know how to exclude class files (as opposed to jar files), please answer this question:http://stackoverflow.com/questions/2885852/how-to-exclude-r-class-files-from-a-proguard-build
Jeremy Bell
A: 

If you are comfortable using the Gradle build tool, then the Android plugin for Gradle makes this whole process extremely simple. See: http://wiki.github.com/jvoegele/gradle-android-plugin/

Jason Voegele
If gradle is an option, then so is SBT - and I've already had success in using SBT. The real catch here is the different directory structures expected by external build systems and the eclipse/android plugin.
Kevin Wright