Scala on Android - Setting SBT configuration

Scala on Android

In our previous post in this series, we gave you an overview about how to prepare the environment for starting to use Scala in your Android applications. In this post, we’re teaching you how to set your SBT configurations.

What is SBT?

As we explained in our previous post, SBT is a build system which helps you to compile, run, test, or package your projects. There are other alternatives, such as Ant, Maven or Gradle, but unlike those, SBT provides a more comfortable development environment for Scala-based projects.

Some of the main features of SBT are:

  • Incremental recompilation improves compilation times, where only the modified files and the needed dependencies are recompiled
  • Continuous compilation and testing with triggered execution
  • Possibility of starting the Scala REPL with project classes and dependencies on the classpath
  • Because it’s based on the Scala language, we can use the full flexibility of the Scala code for building our projects
  • Support for mixed Java/Scala projects

The directory structure

Before we explain the different options that SBT offers us upon building our project, let’s start by talking about how the directories and files in a new Android project are organized.

Base directory

The BaseDirectory is the directory where all the files of the project are located. This value can be used in our build definitions for creating relative paths from the root folder of the project.

Source code

As you can see in the example below, SBT organizes the source code of the project the same way that Maven does.

src/
  main/
    resources/
       <files to include in main jar here>
    scala/
       <main Scala sources>
    java/
       <main Java sources>
  test/
    resources
       <files to include in test jar here>
    scala/
       <test Scala sources>
    java/
       <test Java sources>

SBT build definition files

In smaller projects, having one or two SBT files to form the build definition of our project is enough, but whenever the project grows, organizing the build definition in several files might be preferable.

For convention, we usually name these files as build.sbt and Build.scala, but you can use any name you prefer.

build.sbt
project/
  plugin.sbt
  Build.scala
  <Other Scala files>

You may be wondering: what is the difference between using sbt and scala files?

According to the recommended approach, you should use build.sbt for defining most settings of the project and use the Scala files included in the project folder for implementing tasks or sharing key values, like dependencies, versions, or other values.

Writing your build.sbt

Now that we’ve shown you the advantages of using SBT and how the build definition is structured, let’s start writing the first build.sbt file for our project.

There are three distinct flavors of Build Definition:

  • Multi-project SBT build definition
  • Bare SBT build definition
  • Scala build definition

In this article, we’ll create a Bare SBT build definition. This kind of build definition contains a list of Setting expressions. Each Setting expression is a key-value pair and there are three types of these expressions which are determined by the key:

  • SettingKey[T]: a key for a value computed once (the value is computed when loading the project, and kept around)
  • TaskKey[T]: a key for a value, called a task, that has to be recomputed each time, potentially with side effects
  • InputKey[T]: a key for a task that has command line arguments as input. Check out Input Tasks for more details

Setting expressions

For our build definition, we’ll focus on the two first type of Setting expressions: SettingKey and TaskKey.

In the example below, we are establishing some general info like the name and the version of the project, data related to the organization and the Scala version that we are using in our project.

name := "scala-on-android"

organization := "com.fortysevendeg"

organizationName := "47 Degrees"

organizationHomepage := Some(new URL("https://www.47deg.com"))

version := 0.1.0

scalaVersion := 2.11.6

As we can see in the example, to set a value to a specific key, we use the method :=. This method adds or replaces the value assigned to the key in the sbt’s map. But there are other methods which are used when the key’s value type is a sequence:

  • += will append a single element to the sequence
  • ++= will concatenate another sequence

For example, it’s possible to specify some options to the Scala compiler with the following key:

scalacOptions ++= Seq("-feature", "-deprecation")

How to include library dependencies

In this section, we are going to teach you how to add library dependencies into your projects in SBT. As you probably know, there are two ways of adding dependencies: unmanaged and managed dependencies.

Unmanaged dependencies

When we talk about unmanaged dependencies, we are simply referring to adding the jar files to the lib folder so they will be placed in the classpath of the project.

SBT allows you to customize the folder in which the unmanaged dependencies are located. To do that, you could use the unmanagedBase key and indicate the new folder where you will place the jar files:

unmanagedBase := baseDirectory.value / "custom_lib"

Managed dependencies

This method of adding dependencies is often used by people in general. Managed dependencies is based on the Apache Ivy dependency manager, which is popular for its flexibility and simplicity.

To add a new library dependency into our project, we have to set a value for the libraryDependencies key. The value type associated to this key is a sequence of ModuleId so, as we mentioned above, you should use the += or ++= methods.

The general structure for declaring a dependency looks like the following example, where groupId, artifactId, and revision are strings. The last element (configuration) is optional, it can take a string or a Configuration val as value.

libraryDependencies += «groupID» % «artifactID» % «revision» «[% configuration]»
»groupID|This field represents the name of the organization«
»artifactID|This field represents the name of the module«
»revision|This field represents the version of the library«
»[% configuration]|This field can take some values like <code>test</code> for including this dependency in the classpath only for Test configuration«

For example, here we’ll add some dependencies to our project:

libraryDependencies ++= Seq(
  aar("com.android.support" %  "cardview-v7" % "22.0.0"),
  aar("com.android.support" % "appcompat-v7" % "22.0.0"),
  aar("com.android.support" % "recyclerview-v7" % "22.0.0"),
  aar("com.google.android.gms" % "play-services-base" % "6.5.87"),
  "com.typesafe.play" %% "play-json" % "2.3.6",
  "org.specs2" %% "specs2-core" % "2.4.15" % "test",
  "org.specs2" % "specs2-mock_2.11" %  "3.0-M2" % "test")

As you can see, in some dependencies we use a double %% between the groupId and the artifactId instead of a simple %. What does that mean?

Well, in some instances, a library dependency could be compiled for multiple Scala versions. If we put the double %% in a dependency, sbt will append the Scala version to the artifact name. For example, in our build definition, the artifact name for specs2 will be replaced by specs2-core_2.11.6 and SBT will get the library that matches our Scala version.

There are some dependencies included in the libraryDependencies key which are enclosed within the aar method. We will explain that a bit later in the post, because this method allows us to add Android library packages.

Managing resolvers

For getting all the libraries included as dependencies in our project, SBT uses the standard Maven2 repository by default. In the case that one of the dependencies isn’t located in the default repositories, we need to add a new resolver to the build definition in order for Ivy to find such a dependency.

The way of adding a new resolver is:

resolvers += name at location

For example, if we want to add a new resolver for Sonatype OSS Snapshots, we have to include the following expression:

resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"

You can also tell SBT to search inside your local Maven repository. To do that, just add the resolver shown in the example below.

resolvers += Resolver.mavenLocal

Android-sdk-plugin

As we mentioned in the previous post, we usually use an SBT plugin developed by Perry Nguyen called android-sdk-plugin. This plugin provides useful SBT keys like SettingKeys and TaskKey which allow us to build our Android projects more easily.

To use this plugin in our projects, we simply add a plugin.sbt file into project folder and write this line:

addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.3.22")

The next lines that we have to include in the build.sbt file are one line indicating that we are going to use the Android plugin and another one which specifies the Android target SDK version.

android.Plugin.androidBuild

platformTarget in Android := "android-21"

Once we have added these two lines to the project, we’re then able to add some specific keys about Android projects that android-dk-plugin provides us.

For example:

«run <<= run in Android»

«proguardScala in Android := true»

«useProguard in Android := true»

«proguardOptions in Android ++= Settings.proguardCommons ++ Settings.proguardAkka»

«apkbuildExcludes in Android ++= Seq("META-INF/LICENSE.txt", "META-INF/NOTICE.txt", "META-INF/LICENSE", "META-INF/NOTICE")»
»run|Override the run task with the android:run«
»proguardScala|Activate proguard for Scala«
»useProguard|Activate proguard for Android«
»proguardOptions|Set proguard options«
»apkbuildExcludes|Exclude the given files when apk is built for skipping/ignoring DuplicateFileException«

Adding Android library packages (aar)

As we mentioned before, we might need add some Android library packages to our project. The way that android-sdk-plugin allows us to add these dependencies is by using the aar method.

So, if we need the appcompat-v7 library, we just add the following line into the libraryDependencies key.

libraryDependencies ++= Seq(
	  aar("com.android.support" %  "appcompat-v7" % "22.0.0"))

Releasing your package

If you want to build a release APK of the project, you have to run the android:package-release command in the SBT console. This command builds the APK and signs it with a release key if configured.

To customize the process of building a release APK, android-sdk-plugin provides us some SettingKey and TaskKey expressions. Let’s take a look at these keys:

«packageRelease <<= (packageRelease in Android).dependsOn(setDebugTask(false))»

apkSigningConfig in Android := Option(
  PromptPasswordsSigningConfig(
    keystore = new File(Path.userHome.absolutePath + "/.android/signed.keystore"),
    alias = "my-password"))
»packageRelease|Override the packageRelease task with the android:packageRelease, indicating the setDebugTask(false) task have to be called before the packageRelease task starts«

With these settings, we’ll be able to use the libraries included as dependencies in the build definition in our code and build and sign a release APK. You can also add new settings into the build definition; just take a look at the SBT tutorial page for more info about other available SBT keys.

As in the previous post, you can see the content of the rest of the files in this repository created for the series.

In our upcoming posts, we’ll continue talking about how to use Scala for Android development. In the meantime, if you have any questions, feel free to get in touch with us on Facebook, Twitter, or in the comments below.

blog comments powered by Disqus

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.