Getting started with the new Android build system

As many of you might be aware, the Android Development Tools team is working on a brand new build system. The new system has been in development for a while now, but is being officially announced at Google I/O 2013. The updated build system does away with Ant and moves to the Gradle build automation tool. You can read more about Gradle at gradle.org. The new Android build system is documented on the Android Developer Tools site at tools.android.com.

Getting started with the new build system is relatively easy. To get started you’ll need to make sure you have both Maven and Gradle installed.

Install Homebrew

If you’re working on OS X you can make your life a whole lot easier by installing Homebrew. If you don’t already have it installed, you can install it by running the following command from your terminal:

$ ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

Install Maven

    $ brew install maven

Install Gradle

As of May, 2013, the new Android build system only supports Gradle versions 1.3/1.4. This means it won’t work with the latest version of Gradle. You’ll have to follow the instructions at gradle.org/installation to install a prior version of Gradle. Otherwise you could simply install the latest version using Homebrew.

    $ brew install gradle

You can verify you have the correct version of Gradle installed using the following command:

    $ gradle -v

You should see output that looks something like this:

 
    ------------------------------------------------------------
    Gradle 1.3
    ------------------------------------------------------------
 
    Gradle build time: Tuesday, November 20, 2012 11:37:38 AM UTC
    Groovy: 1.8.6
    Ant: Apache Ant(TM) version 1.8.4 compiled on May 22 2012
    Ivy: 2.2.0
    JVM: 1.6.0_45 (Apple Inc. 20.45-b01-451)
    OS: Mac OS X 10.8.3 x86_64

Add a build.gradle to your project

Add the following build.gradle file to the root of your Android project:

Build your project

You should now be able to build your project using the following command:

    $ gradle clean installDebug

Updating your app to support 7-inch tablets

Recently I’ve spent a lot of time thinking about how to optimize our app layouts for 7-inch tablets like the Nexus 7. It’s not an easy process, but a few tricks will make your life a whole lot easier.

As of today, the two most common screen resolutions in the 7-inch category are 1280x800 - WXGA and 1024x600 - WSVGA. Higher-end tablets like the Nexus 7 have a 1280x800 resolution and thus a higher pixel density. Lower-end tablets like the Samsung Galaxy Tab use a 1024x600 resolution and have a lower pixel density.

The Nexus 7 has a pixel density of around 213ppi and technically falls into the unique tvdpi density bucket, but for all intents and purposes you can consider it an hdpi device. Lower-end devices with a 1024x600 resolution have a pixel density of approximately 170ppi and will typically use mdpi assets. I don’t know of any 7-inch devices with an xhdpi pixel density, but I’m sure we’ll see some hit the market soon.

Regardless of the resolution, all 7-inch Android devices fall into the 600dp category. That is, they have a width of 600 density-independent pixels when in portrait orientation. We can use this width to provide alternative resources for these devices.

For starters, if you want to specify a custom layout for 7-inch devices you can simply add a new res/layout-sw600dp directory. Any layouts in this directory will be applied to devices that have a “smallest-width” of “600dp”.

If you don’t want to provide a completely different layout, but want to bump up the size of all your elements slightly you can use the res/values-sw600dp directory. Any resources provided here like dimens.xml or styles.xml will take precedence on devices like the Nexus 7. This way you can have a single layout file that references dimensions or styles that are dynamic based on the device size.

Finally, you might want to provide alternative drawable resources for 7-inch tablets. You can combine the sw600dp resource qualifier with a density qualifier to provide alternative drawables:

  • res/drawable-sw600dp-mdpi
  • res/drawable-sw600dp-hdpi
  • res/drawable-sw600dp-xhdpi

As you can see we’ve simply added the sw600dp qualifier to our typical drawable directories. These resources will be applied to devices of the correct screen density, but only if they are 7-inch tablets or larger.

All of these techniques are also applicable to targeting 10-inch tablets. Simply replace the 600dp width qualifier with 720dp in the case of 10-inch devices.

Further reading

Styling the Android Action Bar title using a custom typeface

Styling the Action Bar in Android can frequently seem difficult, if not impossible. It’s easy to do wrong and hard to do right. However, customizing the Action Bar title with a custom typeface is a surprisingly easy way to spruce up your app design.

There are several posts on Stack Overflow that recommend accomplishing this by leveraging the Action Bar’s custom view feature, or by getting a reference to the title TextView. These solutions are adequate but can lead to infuriating edge cases. We can do better.

You might have come across the TypefaceSpan class, which allows you to style a section of text in a TextView with a monospace or serif font. This is almost what we want to do, but we need to be able to provide our own Typeface instance.

Here’s what a custom TypefaceSpan might look like in use:

    SpannableString s = new SpannableString("My Title");
    s.setSpan(new TypefaceSpan(this, "MyTypeface.otf"), 0, s.length(),
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 
    // Update the action bar title with the TypefaceSpan instance
    ActionBar actionBar = getActionBar();
    actionBar.setTitle(s);

Notice that we simply create a SpannableString with our desired Action Bar title. We then create a new instance of our custom TypefaceSpan providing it with our Activity context and the typeface name as arguments (so it can load the typeface from our application’s assets directory). The span is set on the entire length of the title (... 0, s.length(), ...). Finally, we pass in the Spannable.SPAN_EXCLUSIVE_EXCLUSIVE flag, which simply indicates that the span should be removed if all of the spanned text is deleted. Here’s what the result might look like:

android_action_bar_typeface_bender

android_action_bar_typeface_cubano

android_action_bar_typeface_gotham

But what does the custom TypefaceSpan implementation look like? Well, it’s surprisingly simple. I’ve created an example you can use in your own apps. You can get it at gist.github.com/twaddington/b91341ea5615698b53b8. Simply copy this class into your application and use it like I’ve indicated above. You can even subclass the custom TypefaceSpan to add additional styles like colors or text shadows.

Fonts Shown

Android TextView with custom Typeface support

Currently Android does not have a native View class that supports defining a custom Typeface in your layout’s XML. After inflating your layout you can obtain a reference to the TextView and call the setTypeface(android.graphics.Typeface) method from your Java code, but this results in unnecessary duplication of logic and violates the DRY principle. Continue reading

Appcelerator Titanium KrollDict fails to sanitize JSONObject.NULL values

If you’re developing an Appcelerator Titanium module for Android it’s possible that you’ll run into this exception (see bottom of post for full exception trace):

!!! Unable to convert unknown Java object class 'org.json.JSONObject$1' to Js value !!!

It took me a long time to track down the cause of this error, but essentially the issue is in the JNI layer, which passes native Java objects to the Javascript layer does not know how to handle a JSONObject.NULL value. The Titanium wrapper object KrollDict attempts to convert all non-native Java objects into native ones. JSONObject becomes a simple Map, JSONArray becomes an ArrayList and so on. However, the constructor fails to account for instances of the JSONObject.NULL object, which is not a simple primitive.

An easy work around would be to ensure the JSON objects that you’re passing around never have any true JSON null values in them. You can replace them with empty strings, or simply remove the key from the object altogether. This is ultimately the fix we went with. However, a simple two-line patch should solve this issue in the core Titanium code:

From 57868ea5b822468563d4a1be2adb901cce383f11 Mon Sep 17 00:00:00 2001
From: Tristan Waddington <tristan.waddington@gmail.com>
Date: Mon, 4 Jun 2012 14:24:15 -0700
Subject: [PATCH] Update the 'fromJSON' method of 'KrollDict' to map the
 'JSONObject.NULL' Object to a real Java null
 representation.
 
---
 android/titanium/src/java/org/appcelerator/kroll/KrollDict.java |    2 ++
 1 file changed, 2 insertions(+)
 
diff --git a/android/titanium/src/java/org/appcelerator/kroll/KrollDict.java b/android/titanium/src/java/org/appcelerator/kroll/KrollDict.java
index da29217..96017d6 100644
--- a/android/titanium/src/java/org/appcelerator/kroll/KrollDict.java
+++ b/android/titanium/src/java/org/appcelerator/kroll/KrollDict.java
@@ -56,6 +56,8 @@ public static Object fromJSON(Object value) {
 					values[i] = fromJSON(array.get(i));
 				}
 				return values;
+			} else if (value == JSONObject.NULL) {
+				return null;
 			}
 		} catch (JSONException e) {
 			Log.e(TAG, "Error parsing JSON", e);
-- 
1.7.10

I submitted a pull request to the titanium_mobile repository on GitHub, but all contributors are required to sign a Contributor License Agreement (CLA). Not a big deal, but I don’t expect to ever submit another patch to this project, so I’m not inclined to spend a day getting their dev environment bootstrapped and sign my life away for a two-line bug fix (yes, I wrote a passive-aggressive blog post instead).

Unfortunately, they don’t even seem interested in accepting the patch as a simple bug report so someone else can fix it:

If you’re trying to build a platform that’s meant to be developer focused, willful disinterest is not the best response. If you’re curious, here’s the raw patch file and the full exception:

Including ActionBarSherlock as a Git submodule

If you’re using the ActionBarSherlock library in your Android project you can easily stay up to date by including it as git submodule.

$ cd /path/to/project
 
# Initialize the submodule 
$ git submodule add https://github.com/JakeWharton/ActionBarSherlock
 
# Commit your changes
$ git commit -am "Added ActionBarSherlock submodule."
 
# Ignore local changes in the submodule directory
$ git status --ignore-submodules=dirty

To ignore changes to the submodule directory (in case permission or other changes are required) you can add the following to your .gitmodules file:

[submodule "ActionBarSherlock"]
	path = ActionBarSherlock
	url = https://github.com/JakeWharton/ActionBarSherlock
        ignore = dirty

Installing Jenkins on an Ubuntu Amazon EC2 instance

Note: This post was written over four years ago and may no longer work correctly. This post will remain published for posterity.

This tutorial assumes you have already created an Amazon EC2 instance and are able to ssh to it. In our case, we used the Quick Launch Wizard to spin up a 32-bit instance of Ubuntu Server Cloud Guest 11.10 (Oneiric Ocelot).

Once you have a running EC2 instance you’ll need to modify the instance’s security group to open up ports 22, 80 and 443.

The first thing I did was update the EC2 timezone to our local timezone:

$ sudo ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime

Install any version control systems you might like to use:

$ sudo apt-get install git

Then pretty much just follow the installation instructions from the Jenkins Wiki. I’ve copied the actual steps here for posterity.

Installing Jenkins:

$ wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
$ sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ &gt; /etc/apt/sources.list.d/jenkins.list'
$ sudo aptitude update
 
# Note this will install the openjdk dependencies automatically!
$ sudo aptitude install jenkins

Setting up an Apache Proxy for port 80 -> 8080:

$ sudo aptitude install apache2
$ sudo a2enmod proxy
$ sudo a2enmod proxy_http
$ sudo a2enmod vhost_alias
$ sudo a2dissite default
$ touch /etc/apache2/sites-available/jenkins

Use your favorite editor to update /etc/apache2/sites-available/jenkins with the following virtual host configuration:

<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	ServerName ci.company.com
	ServerAlias ci
	ProxyRequests Off
	<Proxy *>
		Order deny,allow
		Allow from all
	</Proxy>
	ProxyPreserveHost on
	ProxyPass / http://localhost:8080/
</VirtualHost>

Enable the new jenkins virtual host and restart apache:

$ sudo a2ensite jenkins
$ sudo apache2ctl restart

Jenkins should now be live and accessible from port 80! Now you can begin configuring Jenkins to your liking. I’ll follow-up with a post highlighting our Jenkins configuration. For now, here are the plugins we’ve chosen to install:

  • Green Balls
  • Post build task
  • Instant Messaging Plugin
  • IRC Plugin
  • Rake plugin
  • Git Plugin
  • Github Plugin
  • GitHub API Plugin
  • Github OAuth Plugin

Add mercurial or git changeset id to your Android app

We recently automated the debug builds of our Android app. As a result, we wanted to include the Mercurial changeset id in our app settings, so we could quickly tell what version of the app someone was running.

What we ended up doing was writing the changset id to a custom properties file that was then copied into the raw directory during a build. That properties file could then be read by our PreferencesActivity on runtime.

In order to do this we wrote a custom build.xml file for ant. Simply copy and paste the following into your build.xml file:

    <!-- Require the hg.revision task during pre-build -->
    <target name="-pre-build" depends="hg.revision" />
 
    <!-- Check to see if a mercurial repository exists in the source dir -->
    <available file=".hg" type="dir" property="hg.present" />
 
    <!-- Get the mercurial changeset id for tip -->
    <target name="hg.revision" description="Store mercurial revision in ${repository.version}" if="hg.present">
        <exec executable="hg" outputproperty="hg.revision" failifexecutionfails="false" errorproperty="">
            <arg value="id" />
            <arg value="-i" />
            <arg value="-n" />
            <arg value="-r" />
            <arg value="tip" />
        </exec>
        <echo message="Repository version is ${hg.revision}" />
 
        <!-- Create property file containing mercurial changeset id -->
        <propertyfile file="version.properties" comment="The changset id that this app was built from.">
            <entry key="changeset" value="${hg.revision}" />
        </propertyfile>
 
        <!-- Move property file to app accessible res/raw/ directory -->
        <move file="version.properties" todir="res/raw/" />
    </target>

Essentially this creates an executable task that calls hg id -i -n -r tip in the build workspace. It then adds the result of that command to a propertyfile called version.properties. That file is then copied to the res/raw/ directory.

Our app code is even more straightforward. We simply wrote a function to retrieve the changeset id from the properties file in the raw resources directory. We then take the output of this function and update the preference item.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.settings);
 
        // Display app_build in settings as set in the app.version properties file at build time
        Preference build = findPreference("app_build");
        build.setSummary(this.getAppChangsetFromPropertiesFile());
    }
 
    public String getAppChangsetFromPropertiesFile() {
        Resources resources = getResources();
 
        try {
            InputStream rawResource = resources.openRawResource(R.raw.version);
            Properties properties = new Properties();
            properties.load(rawResource);
            return properties.getProperty("changeset");
        } catch (IOException e) {
            Log.e(TAG, "Cannot load app version properties file", e);
        }
 
        return null;
    }

Android app build automation with Jenkins

In a nutshell, Jenkins provides an easy-to-use so-called continuous integration system, making it easier for developers to integrate changes to the project, and making it easier for users to obtain a fresh build.

This post assumes you have a working ci server running Jenkins (or Hudson).

First download and install the Android SDK to your Hudson server. Make sure it’s in a directory that’s accessible by your Hudson user.

$ sudo su hudson
$ cd ~/
$ curl -lO http://dl.google.com/android/android-sdk_r11-mac_x86.zip
$ unzip android-sdk_r11-mac_x86.zip

After unpacking the SDK you’ll need to install the individual Android platforms.

$ cd android-sdk-mac_x86/
$ android update sdk --no-ui

Now go get some coffee or take a smoke break, this is going to take a while.

Phew. Okay, now that’s done we can configure our new Hudson task. I’m going to assume you’re somewhat familiar with boot strapping a new project in Hudson, so I’m going to gloss over some of the details. Just make sure it’s pointed at your Android app’s source code repository.

In the build section of the project config you’ll want to specify a new “Execute Shell” build step with the following script.

# Ensure the SDK is in the Hudson user's system path
PATH=/Users/hudson/android-sdk-mac_x86/tools/:$PATH
 
# Change to the Hudson workspace directory
cd $WORKSPACE
 
# Create the required build files in the workspace
android --verbose update project --path .
 
# Execute the build
ant clean debug
 
# Copy the apk out of the workspace so your testers can get at your fresh build
scp bin/YourAppName-debug.apk foo:bar/

Pro Tip: Add this build script to your code repository and simply execute that script from Hudson. That way you can track your changes.

Note that if you’ve bundled extra libraries into your app you may see a build error when running this code. If so, try executing the ant build with the -lib option like so.

$ ant clean debug -lib foo/bar/libdir/

If you get into trouble, know that you can execute this build from the console as the Hudson user. Try running through the script manually first so you can identify any errors.

Update (2012-02-01): After living with this set up for a few months we started to run into some issues. It turns out, running your Ant builds from a shell script means Jenkins won’t always notice that your builds are failing. We also started running JUnit tests that, when a test failed, did not also trigger a build failure.

As a result we discovered that it was far better to run your Android builds using the Jenkin’s Ant Plugin. This allows you to invoke a series of Ant commands like clean debug or clean release. Jenkins will also format the Ant output and properly mark your build as failed or successful.

You can also take advantage of the Post Build Task plugin to run a script after your build is successful. This is useful for copying your .apk files elsewhere.

Don’t forget to run $ android update project --path . before your Ant commands are invoked, or your Android SDK folder may not be properly linked to your project.

Update (2012-09-06): The Android Emulator Plugin for Jenkins is a great tool that will help you manage the Android SDK on your CI server. It’s a far better option than installing the tools yourself. While you’re at it, you should check out the Android Lint Plugin to make sure your project is in tip-top shape.

Update (2012-03-02): I recently did a talk on Android build automation with Ant and Jenkins at the 2013 Snow-Mobile conference in Madison, Wisconsin. You can view the slides from the talk online at docs.google.com/presentation/d/19ddXSEVfd6-bGItNfROJMlL6O2rA1XOcRiBdE3NpgV4/edit?usp=sharing.