While most of us will agree that a growing developer audience contributes to the success of a new programming platform we too often neglect the importance of helpful sample applications during its early adoption phase.
Besides its conceptual merits the Android framework provides a powerful and well documented development environment with a plenty of nice Java code examples ! But we're still unsatisfied and we're looking for even more fun with the development of Android applications... so let's translate those sample applications into Scala !
In the following we resume our programming experiences with the translation of Java source code to Scala. We start our code review with basic examples and then focus on more advanced cases.
We present here source code excerpts from concrete Android projects written
in Java respectively in Scala and available in the .zip
archives
android-sdk.zip
,
unlocking-android.zip
and
apps-for-android.zip
.
In Scala, class properties
(aka. non-private var members) implicitly define a getter and a setter
method with them. We illustrate that feature with the
WidgetBean
class declaration from the
WidgetExplorer
project (available in
unlocking-android.zip
).
// WidgetExplorer.java class WidgetBean { public String name; public String type; public String category; // ... (skipped) public WidgetBean(final String name, final String type, final String category /* ... (skipped) */) { this.name = name; this.type = type; this.category = category; // ... (skipped) } @Override public String toString() { return this.name + "\n" + this.type + " " + this.category; } }
// WidgetExplorer.scala class WidgetBean(var id: Long, name: String, var typ: String, category: String, var create: Long, updated: Long) { override def toString: String = name + "\n" + typ + " " + category }
Unlike in Java method parameters in Scala are read-only; in Java you
explicitly write final Paint paint
to prevent local modifications.
// MapViewCompassDemo.java void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { if (paint == null) { paint = mSmooth; } else { paint.setFilterBitmap(true); } delegate.drawBitmap(bitmap, src, dst, paint); }
// MapViewCompassDemo.scala def drawBitmap(bitmap: Bitmap, src: Rect, dst: RectF, paint: Paint) { val p = if (paint == null) mSmooth else { paint setFilterBitmap true paint } delegate.drawBitmap(bitmap, src, dst, p) }
Tuple expressions in Scala are
very handy when a (side-effect free) computation needs to return more
than one value; here is a code example with a pair of values (see the
ApiDemos
project in android-sdk.zip
)
used in a switch
statement:
// Animation2.java void onItemSelected(AdapterView parent, View v, int position, long id) { switch (position) { case 0: mFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_in)); mFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.push_up_out)); break; // ... (more cases) default: mFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.hyperspace_in)); mFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.hyperspace_out)); break; } }
// Animation2.scala def onItemSelected(parent: AdapterView[_], v: View, position: Int, id: Long) { val (in, out) = position match { case 0 => (R.anim.push_up_in, R.anim.push_up_out) // ...(more cases) case _ => (R.anim.hyperspace_in, R.anim.hyperspace_out) } mFlipper setInAnimation AnimationUtils.loadAnimation(this, in) mFlipper setOutAnimation AnimationUtils.loadAnimation(this, out) }
XML literals are natively supported by the language
Scala; in particular they are
type-checked at compile time while Java/Scala strings
containing HTML tags are no more than sequences of characters!
We illustrate that feature with the following code excerpt from the
ApiDemos
project (available in android-sdk.zip
).
// Focus1.java WebView webView = (WebView) findViewById(R.id.rssWebView); webView.loadData( "<html><body>Can I focus?<br />" + "<a href=\"#\">No I cannot!</a>.</body></html>", "text/html", "utf-8");
// Focus1.scala val webView = findViewById(R.id.rssWebView).asInstanceOf[WebView] webView.loadData( <html> <body> Can I focus?<br /><a href="#">No I cannot!</a>. </body> </html>.toString, "text/html", "utf-8")
Match expressions in Scala are a generalization of
Java-style switch statements. However they behave differently in two
ways: Scala’s
alternative expressions never "fall through" into the next case and
they define disjunct local scopes (in Java you must explicitly add a
break
statement and wrap your
code into a local block statement).
// FileManagerActivity.java @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_NEW_FOLDER: LayoutInflater inflater = LayoutInflater.from(this); /* .. (skipped) */ break; case DIALOG_DELETE: /* .. (skipped) */ break; case DIALOG_RENAME: inflater = LayoutInflater.from(this); /* .. (skipped) */ break; // .. (more cases) } }
// FileManagerActivity.scala override def onCreateDialog(id: Int): Dialog = id match { case DIALOG_NEW_FOLDER => val inflater = LayoutInflater.from(this) // .. (skipped) case DIALOG_DELETE => // .. (skipped) case DIALOG_RENAME => val inflater = LayoutInflater.from(this) // .. (skipped) // .. (more cases) }
Note: With the command-line option
-Xlint
the Java compiler will produce the following warning message in presence of (potentially dangerous) "fall-through" code:warning: [fallthrough] possible fall-through into case
.
In Java nested classes such as android.os.PowerManager.WakeLock
can be specified directly in import
directive; in Scala you use the type projection
PowerManager#WakeLock
to reference the type member
WakeLock
of type PowerManager
(see SLS 3.2.2).
// AccelerometerPlayActivity.java import android.content.Context; // [1] import android.os.PowerManager; import android.os.PowerManager.WakeLock; // [2] public class AccelerometerPlayActivity extends Activity { private WakeLock mWakeLock; // .. (omitted) @Override public void onCreate(Bundle savedInstanceState) { mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); } // .. (omitted) }
// AccelerometerPlayActivity.scala import android.content.Context, Context._ // [1] import android.os.PowerManager class AccelerometerPlayActivity extends Activity { private var mWakeLock: PowerManager#WakeLock = _ // [2] // .. (omitted) override def onCreate(savedInstanceState: Bundle) { // .. (omitted) mSensorManager = getSystemService(SENSOR_SERVICE).asInstanceOf[SensorManager] } // .. (omitted) }
Enumerations in Scala require
no language support as in Java (the enum
language construct was introduced in Java 1.5).
// Languages.java static enum Language { BULGARIAN("bg", "Bulgarian", R.drawable.bg), CATALAN("ca", "Catalan"), // ... (many more) private String mShortName; private String mLongName; private int mFlag; private Language(String s, String n, int flag) { /*..*/ } private Language(String s, String n) { /*..*/ } }
// Languages.scala object Language extends Enumeration { val BULGARIAN = Lang("bg", "Bulgarian", R.drawable.bg) val CATALAN = Lang("ca", "Catalan") // ... (many more) case class Lang(shortName: String, longName: String, flag: Int = -1) extends Val(shortName) { /*..*/ } } type Language = Language.Lang
The apply
method in Scala
can reserve strange surprises to Java programmers. In the following
code the name ambiguity can be solved using either renaming or
qualification.
// SoundRecordingDemo.java Button startRecording = (Button)findViewById(R.id.startrecording); startRecording.setOnClickListener(new View.OnClickListener(){ public void onClick(View v) { startRecording(); } }); void startRecording() { /*..*/ }
// SoundRecordingDemo.scala val startRecording = // [1] findViewById(R.id.startrecording).asInstanceOf[Button] startRecording setOnClickListener new View.OnClickListener(){ def onClick(v: View) { // startRecording() // doesn't compile ([1] or [2] ?) SoundRecordingDemo.this.startRecording() } } def startRecording() { /*..*/ } // [2]
Java static members which are combined with class inheritance
may require some glue code to access them from Scala code. For instance,
the Google APIs documentation describes drawAt
as a
convenience method to draw a Drawable
at an offset.
// Overlay.java package com.google.android.maps; public abstract class Overlay { void draw(Canvas canvas, MapView mapView, boolean shadow) /*..*/ protected static void drawAt(Canvas canvas, Drawable drawable, int x, int y, boolean shadow) //.. }
In the following example we want to access the above method
drawAt
but end up with the error message
"drawAt(test.Canvas,test.Drawable,int,int,boolean) has protected access in com.google.android.maps.Overlay"
:
// ViewMap.scala (apps-for-android/panoramio) import com.google.android.maps.*; class ViewMap extends MapActivity { //.. class PanoramioOverlay extends Overlay { override def draw(canvas: Canvas, mapView: MapView, shadow: Boolean) { //.. Overlay.drawAt(canvas, mMarker, x, y, shadow) } } }
To solve that issue we provide separate access to static and instance
members of the library class com.google.android.maps.Overlay
by
introducing the compatibility class scala.android.maps.Overlay
(packaged in <project>/libs/scala-android.jar
together
with other compatibility classes):
// Overlay.scala package scala.android.maps abstract class Overlay extends com.google.android.maps.Overlay { protected object Overlay { def drawAt(canvas: android.graphics.Canvas, drawable: android.graphics.drawable.Drawable, x: Int, y: Int, shadow: Boolean) { Overlay$.drawAt$(canvas, drawable, x, y, shadow) } } }
// Overlay$.java (package protected) package scala.android.maps; abstract class Overlay$ extends com.google.android.maps.Overlay { static void drawAt$(android.graphics.Canvas canvas, android.graphics.drawable.Drawable drawable, int x, int y, boolean shadow) { drawAt(canvas, drawable, x, y, shadow); } }
scala-android.jar
library in our article
"Writing a Scala API for Android".
We came across an unexpected behavior of Java private fields when
translating the AccelerometerPlayActivity
class of the
Android project AccelerometerPlay
. We illustrate that
issue with the following code snippet:
// test.java //class Car { private int price = 36200; } // [2] public class test { class Car { private int price = 36200; } // [1] class B { B() { Car car = new Car(); System.out.println(car.x); } } test() { new B(); } public static void main(String[] args) { new test(); } }
which prints the value 36200
to the console output.
Moving the nested class Car
([1]
<—>[2]
) to the top-level
scope will produce the following compiler error:
[tmp]$ rm -f *.class && javac test.java && java test
test.java:5: x has private access in Car
B() { System.out.println(new Car().x); }
^
1 error
The Scala compiler rejects both variants of the above code.
// test.scala class Car { private var x = 36200 } class test { //class Car { private var price = 36200 } class B { val car = new Car println(car.x) } new B } object test extends App { new test }
We end up with a pitfall Scala programmers may fall into when using default parameters to achieve more concise code in their Android applications.
// GridLayout.java public class GridLayout extends ViewGroup { // .. public GridLayout(Context context) { this(context, null); } public GridLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public GridLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // .. } }
The constructor declarations in the above Java code could for instance be rewritten in Scala:
// GridLayout.scala class GridLayout(context: Context, attrs: AttributeSet = null, defStyle: Int = 0) extends ViewGroup(context, attrs, defStyle) { // .. }
Unfortunately the Android runtime will fail when looking for the appropriate constructor signature to inflate the selected layout resource.
E/AndroidRuntime( 432): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.google.android.photostream com.google.android.photostream.PhotostreamActivity}: android.view.InflateException: Binary XML file line #25: Error inflating class com.google.android.photostream.GridLayout