Android and Gradle : migrating to the new project structure

Recently, I decided to sit down and restructure an app the Gradle way. I had previously migrated the project to Gradle, and made the switch to Android Studio, but I had kept the legacy project structure:

This app has 2 flavors: free and paid. To support them, all of the common code was in a library project, which was used by free and paid projects, for a total of 3 projects. There was a lot of duplicated code with the multiple manifests, and wrapper classes, as well as code interrogating the package name to determine which features should be enabled. Gradle solves all of that mess quite nicely.

To start, I moved all of the library project code into the default Gradle structure, which looked something like this:

This puts everything under the src directory, where Gradle expects to find its source sets. Since this code was common to both the free and paid flavors of the app, everything went into the main source set.

As those files were now in the default locations, I removed all of these custom locations from build.gradle.

I also switched the plugin in build.gradle from android-library to com.android.application, since this would become my single project for building both flavors of the app.

At this point, based on the buildTypes in build.gradle, I could assemble debug and release apps. Since I have 2 flavors, I defined them alongside the buildTypes in build.gradle as productFlavors.

With Ant, I had multiple AndroidManifest.xml files which had unique package names, but with Gradle a single file can be used with the package name injected while building. This is done using applicationId, which is built into the com.android.application plugin. Custom variables are also supported. For example, this app uses the Backup API, which requires a key unique to the app. For each productFlavor, I added a variable in the custom set manifestPlaceholders.

Custom variables can then be referenced in the AndroidManifest.xml like this:

If  you’re running into build errors when injecting values, you may need to specify the new manifest merger in build.gradle:

The new manifest merger has been the default since version 11 of the gradle plugin.

The free flavor of the app has Ads in it, which requires defining an activity in the manifest. That definition isn’t need in the paid flavor. This is where the source sets come in. I have an AndroidManifest.xml in the main source set. Alongside the main directory, I added directories for free and paid.

In the free directory, I only need a slimmed down manifest with the differences:

Gradle, through the com.android.application plugin, merges the manifests together, so that the free flavor has everything it needs, without effecting the paid flavor.

The last difference between flavors was the app name. As with the manifest for the free flavor, I created a strings.xml in the paid source set with only the changed strings, as they will be merged with all of the common strings in the main source set.

The result was the only differences to maintain between the two flavors are the manifest and strings files, rather than two entire projects.

Bonus:

Those Backup API keys can be loaded from an external file, so that they’re not necessary shared along with the build.gradle file. For example, a gradle.properties file in the root of the project might contain:

Those variables would then be accessed in the build.gradle file like this:

Android Debug Bridge (ADB) command scripting

The Android Debug Bridge (ADB) is a useful tool for testing and debugging, though it can be cumbersome to use given its broad set of options. For example, testing an app might require frequent authentication, and just sending the username with adb looks like this:

Command aliases can simplify this. In bash, these can be added to the startup script, such as .bashrc:

Now the username can be passed by simply typing:

Windows users can do something similar using Doskey.

The password could be sent using a similar alias:

Since other text might be sent, and those aliases can be chained together, you could setup the following:

Next, add in some keyevents for navigating between the fields:

Now, the whole login flow can be performed using a single command, such as applogin:

This can be handy for simple and frequent app interaction while testing. I use scripts such as these to navigate through initial activities during a fresh app launch to get me to the point where I need to test or debug quickly.

A similar approach to all of this is to use monkeyrunner, or continuing down that route, testing using something like calabash, but for something quick, I find that this fits the bill.

Here is a larger set of adb option aliases, comprising my basic day to day needs:

 

Interesting? 

Do you find this sort of work interesting? Do you love quaint Bucks County Pennsylvania? Are you an amazing Android Developer? Then we would love to hear from you!

Android Image Credit: Copyright Pinada, CC BY-ND 3.0