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: