Written on
Programming Android with Scala
As I've mentioned in my previous blog post, I am working on several Android projects. Once you've done some larger projects and you have already worked with alternative JVM languages like Groovy [0] or Scala [1], one quickly feels the need for reducing common code patterns found in every Android project. That was the main reason I chose to use Scala as main programming language for one of my free-time Android projects. This article is about my experiences with Scala and the language features I found most useful when working with the Android framework.Environment and Prerequisites
First of all: I am an IntelliJ user, working on a MBP. I did not test how writing an Android app with Scala works out in Eclipse. I usually develop all my Android projects in IntelliJ as Eclipse and the Android plugin just did not work out for me (it constantly slows down, hangs, of course your mileage may vary). I installed the android-sdk, scala, sbt etc. with Homebrew brew install scala sbt android-sdk
- Scala: 2.9.1
- Sbt: 0.10.1
- android-sdk: r12
- IntelliJ: 10.5.2
- Mac OSX: 10.7.1
Setting up the project structure and environment
First of all: I don't want to scare you right away, but neither IntelliJ nor Eclipse (to my knowledge) offers support for developing Android projects with Scala in a continuous process from project creation to APK creation. There is no out-of-the-box configuration, you'll need to have some manual or scripted steps when developing outside the standard Android environment. On the other side, IntelliJ offers seperate module facets for Android and Scala projects. When combining the Scala and Android facet you'll get syntax completion for Scala and Android-specific files, automatic R.java file compilation, sbt APK debug package generation (with a separate run configuration) and, well, the joy to use Scala instead of Java for your Android project ;). I don't want to go into details of IntelliJ module configuration, but in case you chose to work with sbt [3] you can use a giter8 template [2] to jump-start with an sbt supported project structure holding an empty Android application and test setup (for more information see [4]).Using Scala for your Activity classes
Once you are done setting up the environment, you can start with your first activity, let's call it HomeActivity. Let's take a look at the Java implementation.
public class HomeActivity extends Activity {
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.home);
}
}
public class HomeActivity extends Activity {
private EditText usernameEditText;
private EditText passwordEditText;
private Button loginButton;
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.home);
usernameEditText = (EditText) findViewById(R.id.usernameEditText);
passwordEditText = (EditText) findViewById(R.id.passwordEditText);
loginButton = (Button) findViewById(R.id.loginButton);
}
}
public class HomeActivity extends Activity {
private EditText usernameEditText;
private EditText passwordEditText;
private Button loginButton;
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.home);
usernameEditText = (EditText) findViewById(R.id.usernameEditText);
passwordEditText = (EditText) findViewById(R.id.passwordEditText);
loginButton = (Button) findViewById(R.id.loginButton);
loginButton.setOnClickListener(new View.OnClickListener() {
public void onClickListener(View view) {
// do some validation stuff
startActivity(new Intent(HomeActivity.this, NextActivity.class));
}
});
}
}
HomeActivity
into a Scala class. First of all we'll consider getting rid of the findViewById method calls and the type casts that have to be done for the return types. The sbt Android plugin generates a special singleton object for that purpose: the TR (typed resources) object. Within that class it creates typed references of type TypedResource for all the UI elements defined in the application's XML layout files.
case class TypedResource[T](id: Int)
object TR {
val usernameEditText = TypedResource[EditText](R.id.usernameEditText)
val passwordEditText = TypedResource[EditText](R.id.passwordEditText)
val loginButton = TypedResource[Button](R.id.loginButton)
}
trait TypedViewHolder {
def view: View
def findView[T](tr: TypedResource[T]) = view.findViewById(tr.id).asInstanceOf[T]
}
trait TypedView extends View with TypedViewHolder { def view = this }
trait TypedActivityHolder {
def activity: Activity
def findView[T](tr: TypedResource[T]) = activity.findViewById(tr.id).asInstanceOf[T]
}
trait TypedActivity extends Activity with TypedActivityHolder { def activity = this }
object TypedResource {
implicit def view2typed(v: View) = new TypedViewHolder { def view = v }
implicit def activity2typed(act: Activity) = new TypedActivityHolder { def activity = act }
}
class HomeActivity with TypedActivity {
override def onCreate(savedState: Bundle) {
super.onCreate(savedState)
val usernameEditText = findView(TR.usernameEditText)
val passwordEditText = findView(TR.passwordEditText)
val loginButton = findView(TR.loginButton)
// ...
}
}
class HomeActivity with TypedActivity {
override def onCreate(savedState: Bundle) {
super.onCreate(savedState)
setContentView(R.layout.home)
val usernameEditText = findView(TR.usernameEditText)
val passwordEditText = findView(TR.passwordEditText)
val loginButton = findView(TR.loginButton)
loginButton.setOnClickListener(new View.OnClickListener() {
override def onClickListener(View view) {
startActivity(new Intent(HomeActivity.this, classOf[NextActivity]))
}
})
}
}
Favorite 1: Lazy Vals
As you can see in the code sample above, we have still the burden to use findView in the onCreate method. If we wanted other methods to access these references we would have to introduce instance variables, which again increases our LOCs. We can avoid that circumstance by using so-called lazy vals. In fact, Scala provides a keyword for specifying lazy attributes: not surprisingly, lazy.
class HomeActivity with TypedActivity {
lazy val usernameEditText = findView(TR.usernameEditText)
lazy val passwordEditText = findView(TR.passwordEditText)
lazy val loginButton = findView(TR.loginButton)
override def onCreate(savedState: Bundle) {
super.onCreate(savedState)
setContentView(R.layout.home)
loginButton.setOnClickListener(new View.OnClickListener() {
override def onClickListener(View view) {
startActivity(new Intent(HomeActivity.this, classOf[NextActivity]))
}
})
}
}
class HomeActivity with TypedActivity {
lazy val usernameEditText = findView(TR.usernameEditText)
lazy val passwordEditText = findView(TR.passwordEditText)
lazy val loginButton = {
val b = findView(TR.loginButton)
b.setOnClickListener(new View.OnClickListener() {
override def onClickListener(View view) {
startActivity(new Intent(HomeActivity.this, classOf[NextActivity]))
}
})
b
}
override def onCreate(savedState: Bundle) {
super.onCreate(savedState)
setContentView(R.layout.home)
}
}
Favorite 2: Implicit type conversions
Wouldn't it be nice to implement the View.OnClickListener interface with a Scala function, like that
loginButton.setOnClickListener((v: View) => startActivity(new Intent(HomeActivity.this, classOf[NextActivity])))
implicit def function2ViewOnClickListener(f: View => Unit) : View.OnClickListener = {
new View.OnClickListener() {
def onClick(view: View) {
f(view)
}
}
}
Favorite 3: Traits
A Java interface is very abstract. It is as abstract as an interface can be (even the interface contracts are omitted ;)). An abstract class is better in a way that it can provide method implementations, but than the problem swaps over to single inheritance as you can't use multiple abstract classes as parent classes. When your classes are defined in a framework context, this gets a real problem because the single inheritance line is most of the time already reserved for framework descending classes, meaning that custom classes extending from framework classes can't extend custom classes anymore. That reuse restriction causes by single inheritance strikes me with its full power in my Java-based Android projects. Let us assume that we need to extend both, class Activity and class ListActivity. Since the framework and Java's single inheritance mechanism forces us to extend both activity classes, there is no way to extend from a common OptionsMenu base class that uniformly creates the application options menu. With Scala we can create a so-called trait. A trait can be imagined as an abstract or concrete class that can be mixed into another class. A Scala class can mix-in several traits, but can only extend from a single descendant class. The Scala guys to to speak of "mix-in classes" instead of "multiple inheritance of classes" since traits differ in some ways from how multiple inheritance is implemented in other programming languages. A trait for the OptionsMenu could be written as:
trait OptionsMenu extends Activity {
override def onCreateOptionsMenu(menu: Menu) : Boolean = {
menu.addSubMenu("Test")
true
}
}
class NextActivity extends ListActivity with OptionsMenu {
// ...
}
trait Activity with TypedActivity with OptionsMenu {
lazy val usernameEditText = findView(TR.usernameEditText)
lazy val passwordEditText = findView(TR.passwordEditText)
lazy val loginButton = findView(TR.loginButton)
val contentView: Int
val init: Unit
override def onCreate(savedInstanceState: Bundle) {
setContentView(contentView)
init
}
}
class HomeActivity extends Activity {
val contentView: Int = R.layout.activity_home
val init = {
loginButton.setOnClickListener( (v: View) => startActivity(new Intent(HomeActivity.this, classOf[NextActivity] )))
}
}
Favorite 4: Pattern matching and Case Classes
If you have ever taken a look at Erlang you'll have with no doubt a clear picture of how integral pattern matching can be a very characteristic programming language property. Scala provides various pattern matching mechanisms by providing the match and case keywords.
override def onCreateDialog(dialogId: Int) : Dialog = {
dialogIg match {
case SHOW_ALERT_DIALOG => // create the dialog instance
case _ => throw new IllegalArgumentException("Dialog " + dialogId + " could not be created!")
}
}
case class ViewHolder (checkBox: CheckBox, textView: TextView)