Scala is a sometimes-intimidating programming language that is Object-Oriented, functional, statically typed and wicked badass. It comes batteries-included, featuring a rich collections API, Type inference, and dead-simple concurrency. Perhaps most poignantly though, it's also a 30MB JVM library that can compile to dalvik.
Come take a heads first dive into crafting an APK with glorious, nutritionally fit Scala. We'll see how we can get more power from less code when wrangling the Android framework to do your bidding. We'll even play with all the basic tools that make this stop seeming like a daunting task and start seeming more like a walk up the stairs.
- I'm nathan dotz
- I work at Detroit Labs
- I'm @nathandotz on twitter
- I'm @sleepynate github and app.net
At Detroit Labs, we have two things we do very often
- write mobile apps
- write sertives for those mobile apps
We used to write a lot of our services in Ruby, but as easy as Ruby is, it is often not the best solution. Concurrency is a pain in the butt, it's generally slower under load than other languages, and a lack of static typing makes for a lot of error-checking code in what should otherwise me straightforward 1-type-allowed requests or responses.
Scala, on the other hand, addresses nearly all of those issues cleanly, and while it appears somewhat scary from the outside, is actually a pleasant mid-ground between nice, fast services written in something clunky like Java and something more pleasant to write in like Ruby or Python. Scala doesn't "feel" like a traditionally statically-typed object-oriented language. More impressively, Scala brings functional paradigms to the table, which are all too apt for writing web services.
After being pleased that we had Scala in production for some web services, it was only natural to ask: "Where else could we apply this handy JVM language?" The answer of course was right in front of us -- we write for dalvik every day on Android.
I remember reading years ago about how slow Scala was on Android and "if only someone would put in some effort maybe some day it will be usable". Luckily, those years have passed and the community has figured out that a combination of updates to Scala, some tuning with ProGuard, and good old-fashioned trial-and-error, using Scala on Android actually incurs a rather low overhead and dramatically decreases the amount of code required for many mundane problems.
(a.k.a stuff we won't be covering but you'll need)
- Android SDK installed
- Scala 2.10 installed
- Maven-deployed Android SDK via maven-android-sdk-deployer
- A template project to make life easier
We'll be writing a simple app to check an RSS feed. Such an app already exists implemented in Java on github. We want to create an activity that contains a list view showing our the title of each RSS item, which when clicked, opens the default URL handler and displays the webpage. Simple, right? Well, despite it being a rather simple task, we can see from the rather sparsely-implemented Java version that this still requires nearly 200 lines of production code with a scant 100 lines of tests.
We should be able to accomplish the same thing in Scala in about 1/3 the number of lines and (in my very opinionated view) much more elegantly to boot.
To get started, we'll use an existing template project.
Simply clone the repository locally, and if you so choose, delete the .git
directory to prepare for uploading it to github yourself.
$ git clone https://github.com/sleepynate/scala-on-android-template-project.git
$ cp -r scala-on-android-template-project androids-under-the-stairs
$ rm -rf androids-under-the-stairs/.git
This repository is forked from Mr. Rohan Singh's
hard work, and includes some basic instructions on how to get maven set up for
Android, as well as including a pre-made pom.xml
already tuned for Scala
development on Android.
Let's do a quick run-down of the important things in our project already:
$ tree androids-under-the-stairs
├── AndroidManifest.xml
├── pom.xml
├── proguard.cfg
├── res
│ ├── layout
│ │ └── main.xml
│ └── values
│ └── strings.xml
├── scala-test.keystore
└── src
└── main
└── scala
└── HelloAndroidActivity.scala
AndroidManifest.xml
, res/
and *.keystore
we should know and recognize.
Nothing special there. We also have a pom.xml
, which has our maven
configuration already set up with plugins for building Scala and Android, as
well as running ProGuard on our application.
Speaking of, there's also a proguard.cfg
which is tuned to keep the most
important aspects of Scala and Android that we are likely to extend and remove
the rest of the unused Scala classes, leaving us with (in optimal cases) around
800kb of overhead.
Finally, we have a source folder set up in standard maven style with the top
level of our packages located at src/main/scala
. Note though, that despite
using the package name com.detroitlabs.thinks.you.are.cool
, scala does not
require us to maintain a directory structure
To prove to ourselves that Scala will actually build and deploy to Android, we can launch our emulator and deploy the demo app to it.
$ cd androids-under-the-stairs
$ $ANDROID_HOME/tools/emulator -avd demo &
$ mvn install android:deploy android:run
After downloading the entirity of the internet and taking a brief sightseeing orbital tour of Mars, maven builds an APK, deploys it to our emulator, and runs it, showing us the squares of the first few natural numbers, because gosh-darnit math is swell.
Looking at our sole source file, even if we've never seen Scala before, it
should look rather familiar. We can see a class which extends
android.app.Activity
and overrides onCreate
and onResume
. Also, we can
see some familiar functionality in Android such as setContentView
,
findViewById
, our compiled R
class, and TextView
being used almost
exactly as they would standard Java for Android.
Just kidding! Let's edit some XML. According to the unit tests provided with the sample app, we need a list view to stick items in, so let's make a quick change to our layout.
In res/layout/main.xml
, we replace the <TextView … />
tag with:
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/feed_list"
/>
Naturally, this will cause our application to no longer compile. We can
reconcile this back in our source file by replacing references to our TextView
with our new ListView
instead. Thus:
import android.widget.TextView
var helloTextView: Option[TextView] = None
helloTextView = Option(findViewById(R.id.helloTextView).asInstanceOf[TextView])
become:
import android.widget.ListView
var feedListView: Option[ListView] = None
feedListView = Option(findViewById(R.id.feed_list).asInstanceOf[ListView])
While this will compile, it doesn't really demonstrate our ListView
, so let's
deploy our old friend ArrayAdapter
to show the squares we've calcuated until
we're ready to figure out our XML parsing. So, we can replace:
val items = Array(1, 2, 3, 4, 5)
val output = items
.map { i => i * i }
.mkString(", ")
helloTextView map { _.setText(output) }
with:
import android.widget.ArrayAdapter
// …
val items = Array(1, 2, 3, 4, 5)
val output = items
.map { i => (i * i).toString }
val adapter = new ArrayAdapter[String](this, android.R.layout.simple_list_item_1, output)
feedListView.get.setAdapter(adapter)
For those unfamiliar with functional programming, these few lines may seem
dense and intimidating. However, after compiling and running the code as it is
now, those familiar with Android might already be able to see some of the power
of Scala's rich collections API shining through. What we've done here is to
take an Array of 5 integers, and then map
a function literal across them
which at once both squares those numbers and then converts them to a string. A
similar operation in Java might look like:
ArrayList<String> output = new ArrayList<String>();
int[] items = {1, 2, 3, 4, 5};
for(int i: items) {
output.add(String.parseInt(i*i));
}
Compare this to a slightly more terse version of what we've included in our Android activity:
val output = Array(1, 2, 3, 4, 5).map { i => (i * i).toString }
There are quite a few nice things going on here which make Scala quite a pleasure to work with:
- Due to type inference, all of the 'noise' of the type declarations disappears. Thankfully, the Scala compiler is smart enough to figure these things out on its own.
- A robust collections API meant that we didn't have to manage the conversion
of our collection from
int[]
toArrayList<String>
manually by extracting elements from one collection, converting their type and adding them to another. - In tandem with the collections API, a functional approach meant that instead of dictating the steps taken to transform our collection, we defined a transformation and applied it to the collection as a whole.
Now that we have a ListView
to display our XML results, it's probably about
time we look into actually retrieving XML to parse and place into said
ListView
. The approach taken with the standard XmlPullParser
included with
Android is, frankly, ugly as sin. The implementation is about as bare-bones as
it gets and weighs in at a mere 50 lines. Luckily, much like the collections
API, Scala includes a glorious XML library with an enjoyably sugary syntax.
Currently, we don't know much about that library or its API. Normally with Java, we'd probably sit down and read through several pages of Javadoc that showed up in a web search before trying out a few promising methods in a separate test implementation before realizing we had an entirely wrong approach while checking stack overflow. Scala saves us a significant amount of pain by allowing us to play around in the REPL.
Let's take a dive! Now, we could simply launch Scala's REPL by typing scala
at the command line. However, sbt
, the de facto build tool used in Scala
projects, provides a "beefed-up" sbt console
which provides extra
functionality such as tab completion that is great for exploring new libraries.
We'll use that instead to make life a little easier on ourselves as we explore
this strange new world…
$ sbt console
[info] Loading global plugins from /Users/sleepynate/.sbt/plugins
[info] Set current project to default-d0f036 (in build file:/private/tmp/)
[info] Starting scala interpreter...
[info]
Welcome to Scala version 2.9.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_43).
Type in expressions to have them evaluated.
Type :help for more information.
scala>
Now, since we've handily looked up that the XML classes we need are located in
the scala.xml
package, we can freely load all the members of this package and
explore! First though, let's take a look at what's available. we'll type
scala.xml.
at the REPL and hit tab, showing all the publically available
members of the package:
scala> scala.xml.
Atom Attribute
Comment Document
// (…)
Unparsed UnprefixedAttribute
Utility XML
XercesClassName Xhtml
dtd factory
include package
parsing persistent
pull transform
Wow, there's a lot there. However, the XML class looks promising. Let's import
the packages members and see what it has for us, again using the tab-completion
avalable in sbt console
scala> import scala.xml._
import scala.xml._
scala> XML.
adapter asInstanceOf encoding
isInstanceOf lang load
// (…)
The load
method looks promising…
scala> XML.load
def load(is: java.io.InputStream): T
def load(reader: java.io.Reader): T
def load(source: org.xml.sax.InputSource): T
def load(sysID: String): T
def load(url: java.net.URL): T
Oh boy! Even without knowing much Scala, it looks like we've found an
XML.load
function that will take a URL
, an InputStream
, or other familiar
Java objects. I wonder if it's as simple as supplying a string with our URL:
scala> XML.load("http://detroitlabs.tumblr.com/rss")
res0: scala.xml.Elem = <rss version="2.0" xmlns:dc=…
It would indeed appear that loading our XML from the web was as simple as that.
Note too, that even though we didn't explicitly bind this result to a val
,
the REPL has done so for us, meaning we can reference this value later through
the symbol res0
scala> res0
res1: scala.xml.Elem = <rss version="2.0" xmlns:dc=…
Using a similar method, with the joys of tab-completion, we can discover that
our result has methods like toList
, flatten
and map
, which indicate we've
likely got a collection on our hands. We can even explore what methods might be
available on a single element of our collection by simply evaluating the first
object and checking its available methods:
scala> res0.head
res2: scala.xml.Node = <rss version="2.0" xmlns:dc=…
We can see the type has changed from Elem
to Node
, etc…
After some playing around with the functions available on our objects, we might end up with something like:
scala> (res0 \\ "item").map { n =>
(n \ "title").text +" - "+ (n \ "link").text
}
res20: scala.collection.immutable.Seq[java.lang.String] =
List(Emochi - http://detroitlabs.tumblr.com/post/47561431779, Nerd …
So, it looks like we can decompose our XML by their node names using the \\
and \
functions. xmlobj \\ "item"
gives us a collection of nodes, and node \ "title"
gives us the value of the "title" tag from within a node. Armed with
this knowledge we've gained by being valiant explorers, we can get back to our
actual implementation in Android land.
Having discovered that we're interested in referring to some set of title/link
combinations, it seems only suitable to create a container in which to store
such pieces of data. In Java, we'd often create a bean for this, or if we were
anticipating passing it around the Android ecosystem, defining a Parcelable
class. Scala provides us a much more convenient method for such cases, and it
is appropriately called the case class
. We'll define one called RssItem
to
handle our data.
case class RssItem(title: String, link: String) {
override def toString = title + " (" + link + ")"
}
A case class
is a special class which defines an object that automatically
exposes the parameters to its constructor, making it a great container. Taking
this class back into our REPL, we can building a nice collection of our
RssItem
s like so:
scala> for (item <- res0 \\ "item") yield RssItem((item\"title").text,
| (item\"link").text)
res25: scala.collection.immutable.Seq[RssItem] =
List(Emochi (http://detroitlabs.tumblr.com/post/47561431779), Nerd Nite …
Additionally, case classes give us some other freebies, such as defining
apply
, unapply
, equals
, and the toString
method we've overridden,
though we won't delve into those here.
It appears we've got a solid parsing implementation. All that is left to do is to combine it with our display code and we'll have successfully ported our application to Scala.
First, let's add our RssItem
class to our source file, as well as creating a
function on our activity's companion object which fetches and parses our RSS
items:
import scala.xml._
case class RssItem(title: String, link: String) {
override def toString = title + " (" + link + ")"
}
object HelloAndroidActivity extends Activity {
// (…)
def fetchRss(url:String) = {
val xml = XML.load(url)
for (item <- xml \\ "item") yield {
val title = (item \ "title").text
val link = (item \ "link").text
RssItem(title, link)
}
}
}
And naturally (because I know like any good Android Developer, you'd never forget this), we'll put the internet permission in our manifest:
<!-- … -->
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
Now that we can load our list of RssItem
s, we can do a minimal implementation
to simply get our list items displaying simply by changing the contents of our
output
val in onResume
:
override def onResume(): Unit = {
super.onResume()
val items = HelloAndroidActivity.fetchRss("http://detroitlabs.tumblr.com/rss")
val output = items map { _.title }
val adapter = new ArrayAdapter[String](this, android.R.layout.simple_list_item_1, output.toArray)
feedListView.get.setAdapter(adapter)
}
Our last bit of click functionality comes from the rather hackish method of
setting an OnItemClickListener
for our ListView
which finds the appropriate
item from the list and launches a browser based on its link
val.
feedListView.get setOnItemClickListener(new OnItemClickListener() {
override def onItemClick(parent: AdapterView[_], view:View, pos:Int, id:Long) {
val browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(items.toArray.apply(pos).link));
startActivity(browserIntent);
}
})