If you don't yet have your Eclipse - ADT - Maven tool-chain set up you might be interested in the previous post. If you are joining a team that has already set up as I describe here you probably want this post instead.

Google's ADT is great if you're working alone, but falls short when a team needs to work on the same Android project. It gets worse when you have multiple projects - especially if some are library projects.

It gets worse still if the development team is distributed (as we are) and/or running different development platforms - Windows, Linux, Mac OSX - (as we do). A description of how I've set things up follows this brief interlude:

Android Maven Eclipse Subversion, a lego comic strip made with Comic Strip It!

The important things I wanted to enable in our team environment are:

  1. That the whole team can "get" the latest code quickly and easily
  2. That the whole team can contribute updates to the codebase quickly and easily
  3. That any new team member coming on-board can build immediately from check-out (given a short list of pre-requisites)
  4. That any team member can easily, consistently and correctly build a signed apk for release to the market
  5. New projects can be created quickly and easily with minimum of re-work and copy-paste in configuration
  6. Componentisation (e.g. jars and apklibs) is a Good Thing, and should be encouraged by making it as straight-forward as possible
  7. Developers have their choice of OS

Here's how I've set things up to support these goals...

Pre-requisites

I am assuming that:

  • You use some form of source-code control (Subversion/GIT/other...). Of course you do :)
  • All developers will install Eclipse and ADT for themselves as a pre-requisite.
  • If, as a team, you use Maven and/or Continuous integration, all developers will also install m2eclipse and m2e-android eclipse plugins and Maven 3 (see previous article).
  • You have some common practices in your team like, for example, checking out all projects as siblings in a single workspace directory (otherwise you'll have problems with sharing relative paths to referenced projects between developers).

Our Setup

I've set up projects in the workspace such that all of the following are siblings in a single workspace directory:

  • A parent project that hosts most of the maven-android config as a parent pom.
  • A project that hosts the keystore, and is checked in to source-code control (I actually use the same project for both the parent pom and keystore).
  • A (Android Library) project that contains a copy of the market licensing code (Google recommend keeping a separate copy outside of the SDK install directory). Ours is checked in to SVN for convenient sharing.
  • Multiple Android library (apklib) projects for our own code that is shared between multiple apps (apk's).
  • Multiple Android (apk) projects

Since most of the maven configuration is provided by the parent pom, each new project requires only minimal configuration. The parent pom for our android projects currently looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="
  http://maven.apache.org/POM/4.0.0 
  http://maven.apache.org/xsd/maven-4.0.0.xsd" 
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany</groupId>
  <artifactId>android</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>
  <build>
    <sourceDirectory>src/java/main</sourceDirectory>
    <pluginManagement>
      <plugins>
        <!--This plugin's configuration is used to store 
                Eclipse m2e settings only. It has no influence 
                on the Maven build itself.-->
        <plugin>
          <groupId>org.eclipse.m2e</groupId>
          <artifactId>lifecycle-mapping</artifactId>
          <version>1.0.0</version>
          <configuration>
            <lifecycleMappingMetadata>
              <pluginExecutions>
                <pluginExecution>
                  <pluginExecutionFilter>
                <groupId>
                  com.jayway.maven.plugins.android.generation2
            </groupId>
                    <artifactId>android-maven-plugin</artifactId>
                    <versionRange>[3.0.0,)</versionRange>
                <goals>
                  <goal>proguard</goal>
                </goals>
              </pluginExecutionFilter>
              <action>
                <ignore></ignore>
              </action>
                </pluginExecution>
              </pluginExecutions>
            </lifecycleMappingMetadata>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>com.jayway.maven.plugins.android.generation2</groupId>
        <artifactId>android-maven-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
          <androidManifestFile>
            ${project.basedir}/AndroidManifest.xml
              </androidManifestFile>
          <assetsDirectory>${project.basedir}/assets</assetsDirectory>
          <resourceDirectory>${project.basedir}/res</resourceDirectory>
          <nativeLibrariesDirectory>
            ${project.basedir}/src/main/native
          </nativeLibrariesDirectory>
          <sdk>
            <platform>14</platform>
          </sdk>
          <proguard>
            <skip>false</skip>
          </proguard>
          <sign>
            <debug>false</debug>
          </sign>
          <deleteConflictingFiles>true</deleteConflictingFiles>
          <undeployBeforeDeploy>true</undeployBeforeDeploy>
        </configuration>
        <extensions>true</extensions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jarsigner-plugin</artifactId>
        <version>1.2</version>
        <executions>
          <execution>
            <id>signing</id>
            <goals>
              <goal>sign</goal>
            </goals>
            <phase>package</phase>
            <inherited>true</inherited>
            <configuration>
              <archiveDirectory></archiveDirectory>
              <includes>
                <include>target/*.apk</include>
              </includes>
              <keystore>../android/keystore</keystore>
              <storepass>keystore-password-goes-here</storepass>
              <keypass>key-password-goes-here</keypass>
              <alias>key-alias-goes-here</alias>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

An example of a pom from a library (apklib) project looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="
    http://maven.apache.org/POM/4.0.0 
    http://maven.apache.org/xsd/maven-4.0.0.xsd" 
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.mycompany</groupId>
    <artifactId>android</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <artifactId>android.util</artifactId>
  <version>1.0.1-SNAPSHOT</version>
  <name>Android Utils</name>
  <packaging>apklib</packaging>
  <description></description>
  <dependencies>
    <dependency>
      <groupId>com.google.android</groupId>
      <artifactId>android</artifactId>
      <version>2.2.1</version>
      <scope>provided</scope>
    </dependency>
        <!-- made available to android by 
             "maven android sdk deployer" -->
    <dependency>
      <groupId>android.support</groupId>
      <artifactId>compatibility-v13</artifactId>
      <version>r6</version>
    </dependency>
    <dependency>
      <groupId>oauth.signpost</groupId>
      <artifactId>signpost-core</artifactId>
      <version>1.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>oauth.signpost</groupId>
      <artifactId>signpost-commonshttp4</artifactId>
      <version>1.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.twitter4j</groupId>
      <artifactId>twitter4j-core</artifactId>
      <version>2.1.0</version>
    </dependency>
  </dependencies>
  <scm>
    <connection>scm:svn:svn://repo/project/trunk</connection>
    <developerConnection>
      scm:svn:svn://repo/project/trunk
    </developerConnection>
  </scm>
</project>

An example pom for an app (apk) project looks like this:

<project xsi:schemaLocation="
  http://maven.apache.org/POM/4.0.0 
  http://maven.apache.org/xsd/maven-4.0.0.xsd" 
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.mycompany</groupId>
    <artifactId>android</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <artifactId>android.ui</artifactId>
  <version>1.0.1-SNAPSHOT</version>
  <packaging>apk</packaging>
  <description></description>
  <dependencies>
    <dependency>
      <groupId>com.mycompany</groupId>
      <artifactId>domain</artifactId>
      <version>1.0.1-SNAPSHOT</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>com.mycompany</groupId>
      <artifactId>android.util</artifactId>
      <version>1.0.1-SNAPSHOT</version>
      <type>apklib</type>
    </dependency>
    <!-- this project contains a copy of 
             the sdk licensing code -->
    <dependency>
      <groupId>com.mycompany</groupId>
      <artifactId>android.licensing</artifactId>
      <version>1.0.0-SNAPSHOT</version>
      <type>apklib</type>
    </dependency>
    <dependency>
      <groupId>com.google.android</groupId>
      <artifactId>android</artifactId>
      <version>2.2.1</version>
      <scope>provided</scope>
    </dependency>
        <!-- made available to android 
             by "maven android sdk deployer" -->
    <dependency>
      <groupId>android.support</groupId>
      <artifactId>compatibility-v13</artifactId>
      <version>r6</version>
    </dependency>
    <dependency>
      <groupId>oauth.signpost</groupId>
      <artifactId>signpost-core</artifactId>
      <version>1.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>oauth.signpost</groupId>
      <artifactId>signpost-commonshttp4</artifactId>
      <version>1.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.twitter4j</groupId>
      <artifactId>twitter4j-core</artifactId>
      <version>2.1.0</version>
    </dependency>
    <!-- prevent commons-logging from being included by 
         the Google HTTP client dependencies, which creates 
         a truck load of warnings and eventually kills eclipse -->
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1.1</version>
        <scope>provided</scope>
    </dependency>
  </dependencies>
  <scm>
    <connection>scm:svn:svn://repo/project/trunk</connection>
    <developerConnection>
      scm:svn:svn://repo/project/trunk
    </developerConnection>
  </scm>
</project>

Building a Release

Building a release, including running proguard to optimise and obfuscate the apk, and signing the apk from the shared keystore is now available from the maven cmdline with (as you'd expect):

mvn clean package

Its still early days for us, so I'm sure there are still wrinkles to iron out, but so far it seems to be working pretty well.

You can start the emulator and deploy the packaged apk into it using two further commands:

mvn android:emulator-start android:deploy

Enjoy!

blog comments powered by Disqus