Scaloid is an open-source library that enables Scala developers to create Android apps without having to migrate to Java. Scaloid takes full advantage of many of Scala's features, such as the efficient way of creating Domain Specific Languages (DSLs), implicit conversions, pattern matching, and type safety. Scaloid proposes a novel method of developing Android apps, which is worth a second look. In this lead article of a two-part series on Scaloid, I explain some of its most interesting features.
Simplifying Android Code with Scala Features
Scaloid focuses on simplifying and reducing the required Android code as much as possible while taking advantage of type safety. You can make incremental use of many Scaloid features in your Android projects because you can mix Scala and Scaloid with Java and the Android API.
Think of Scaloid as a library that provides shortcuts to tasks that usually require a large amount of code. Scaloid replaces the XML layout description required by the Android SDK as well as the Java code that specifies the logic with a single piece of Scaloid code that uses a Scala DSL. In this way, you can build a UI layout by writing type-safe Scala code and wire your logic into the layout. However, you will use Scaloid to access widgets defined in XML layouts, so you can continue working with XML layouts if you don't want to make a big paradigm shift in your Android development process.
There is an automatic layout converter that translates an Android XML layout description into a Scaloid layout at http://layout.scaloid.org. The converter is still a beta version and might lose some precision during translation. However, the converter allows you to easily start working with Scaloid layouts using your existing knowledge of Android XML layouts, and you can still use the graphical layout designers provided by your favorite IDE.
Figure 1 shows a very simple Android UI with two TextView
widgets, two Password
fields and a Button
widget in a LinearLayout
with a vertical orientation.
Figure 1: The graphical layout designer in Eclipse with an Android UI preview.
The following code shows the XML for the UI. It includes hardcoded strings in order to make it easy to understand the conversion to a Scaloid layout. However, as you already know, you shouldn't use hardcoded strings for the UI in real-world apps.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/item_detail_container" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ItemDetailActivity" tools:ignore="MergeRootFrame" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Enter your password" /> <EditText android:id="@+id/password" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:inputType="textPassword" > <requestFocus /> </EditText> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Repeat your password" /> <EditText android:id="@+id/passwordConfirmation" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:inputType="textPassword" /> <Button android:id="@+id/login" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Sign in" /> </LinearLayout> </FrameLayout>
If you paste the XML code in the layout converter at http://layout.scaloid.org and you click on Submit
, the results of the translation will be the following lines of Scala code:
override def onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) contentView = new SFrameLayout { this += new SVerticalLayout { STextView("Enter your password").<<.wrap.>> SEditText().inputType(TEXT_PASSWORD) STextView("Repeat your password").<<.wrap.>> SEditText().inputType(TEXT_PASSWORD) SButton("Sign in") }.<<.fill.>> } }
The layout description with Scaloid (see Figure 2) is greatly reduced compared with the XML code. As you might guess by reading the code, the Scaloid UI definition adds an S
prefix to the equivalent Android SDK classes. For example, Scaloid defines the SFrameLayout
class in the org.scaloid.common
package. SFrameLayout
is a concrete helper class of android.widget.FrameLayout
.
Figure 2: An Android device emulator displaying the UI generated with the Scaloid code.
The Scala code requires the import org.scaloid.common._
import line, and the overridden onCreate
method must be placed in a class that extends the SActivity
(org.scaloid.common.SActivity
) trait. I'll discuss the creation of a Scaloid project from scratch later, but for now, I want to focus on the way Scaloid defines the UI.
The classes with the S
prefix, known as prefixed classes, allow you to use Scala-style getters and setters because they provide the necessary implicit conversions. In addition, these classes provide the instance of Context
as an implicit parameter. As you already know, there are many methods in the Android API that require an instance of the Context
class, and the implicit parameter simplifies the usage of these methods in the prefixed classes. You can declare an implicit value to represent the current context or extend the SContext
trait that defines the implicit value. Because the code defines the onCreate
method within a class that extends SActivity
, the implicit value is already defined. SActivity
extends Activity
with SContext
and other traits, as you can see in the following declaration:
trait SActivity extends Activity with SContext with TraitActivity[SActivity] with Destroyable with Creatable with Registerable {
By reading the Scala code, it is easy to see that you have a SFrameLayout
with a SVerticalLayout
that includes the following five widgets, and you don't need to scroll because the code requires just a few lines:
STextView
SEditView
STextView
SEditView
SButton
All the prefixed classes that extend Android widgets have a companion singleton object that implements apply
methods with different parameters, which create the new component and append it to the layout context that encloses it. For example, the SButton
class is the helper class of android.widget.Button
, and there is also an SButton
object with four apply
methods. You need only write the following line to add a button and set its text to "Sign in."
SButton("Sign in")
This way, you can take advantage of the shortcuts and you don't need to write more code to achieve the same effect, as in the following two lines:
button = new SButton() text "Sign in"
this += button
In the following line, notice the use of two methods: <<
and >>
:
STextView("Enter your password").<<.wrap.>>
The <<
method returns an object of the android.view.ViewGroup.LayoutParams
type that provides many methods and setters in the context of the inner-most layout. In this case, the inner-most layout is SVerticalLayout
. Scaloid uses the following default values for the LayoutParams
object:
width: FILL_PARENT
height: WRAP_CONTENT
The default code would be similar to the following line:
STextView("Enter your password").<<(FILL_PARENT, WRAP_CONTENT)
Scaloid uses two methods to provide shortcuts for frequently used values of width
and height
:
fill
: Sets width toFILL_PARENT
and height toFILL_PARENT
.wrap
: Sets width toWRAP_CONTENT
and height toWRAP_CONTENT
.
The following lines are equivalent. One of them uses wrap
and the other sets the width
and height
values:
STextView("Enter your password").<<.wrap
STextView("Enter your password").<<(WRAP_CONTENT, WRAP_CONTENT)
The >>
method allows you to return the original object. Thus, the following line creates a new TextView
widget, sets its text
to "Enter your password," appends the widget to the enclosing SVerticalLayout,
sets the width and height values for its LayoutParams
object, and finally assigns the STextView
instance to the textView1
variable.
val textView1 = STextView("Enter your password").<<.wrap.>>
In the Scaloid code that defines the UI, you will notice that the SVerticalLayout
declaration ends with the following line:
}.<<.fill.>>
This way, the code sets the width and height values for the LayoutParams
object of the SVerticalLayout
instance, and then goes back to this instance.
You can also interact with a UI defined with classic XML by using the find
method and specifying the widget type. For example, the following line retrieves the signIn
button from the UI definition:
val button = find[Button](R.id.signIn)
This is equivalent to the following longer code:
val button = findViewById(R.id.signIn).asInstanceOf[Button]
The clear disadvantages of accessing UI widgets with either the find
or findViewById
methods are that the code is not type-safe and you have to track the widget type. If you use the wrong widget type, there isn't going to be a compile-time error.