Gradle, Instant Run and an applicationId

Ivan Melnikov
4 min readDec 21, 2018

Oh boy, my first Medium article. Let’s see how I’ll mess this up! 😃

Quite recently, the team at work ran into the following error when attempting to build our application’s APK:

I’ve removed the two package names, but they were along the lines of com.org.app.abc and com.org.app2.abc. Needless to say, following the suggestions shown above did not resolve the issue.

Running ./gradlew build and installing the APK manually worked just fine. All of the suggestions online pointed to disabling Gradle’s Instant Run capability in Android Studio (and yes, that did work). In my mind, this wasn’t a solution, and so I am writing this article in the hopes it’ll help someone. It’s either that, or me putting off wrapping the remainder of the Christmas presents.

Build Configurations

Let’s talk about build configurations. If you’ve ever worked with Android apps, you’ve probably seen this snippet of code in one of your Gradle files:

buildTypes {
debug {
shrinkResources false
minifyEnabled false
}
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
multiDexEnabled true
signingConfig signingConfigs.release
}
}

The snippet tells us that we have two build types — debug and release. The former is used for local development, is probably not signed, is neither minified nor obfuscated. The latter is the opposite — signed, code is minified, and obfuscated, and is generally ready for the Google Play Store.

In conjunction with using build types, product flavors are used as well (as of Gradle 3.0.0, all flavors need to belong to a flavorDimension):

flavorDimensions "whatever"productFlavors {

development {
dimension "whatever"
}

alpha {
dimension "whatever"
}

beta {
dimension "whatever"
}
production {
dimension "whatever"
}
}

Flavors can be used to change the minimum supported API level, inject different keys for your services, put an Easter egg in, change the app icon. A sample workflow can be:

  1. Develop locally with development
  2. Send alpha to QA
  3. Send beta to a larger testing community
  4. Accidentally publish development to Google Play Store…I mean uuuh, release production to the customers 😃

Combining the build type and product flavor gets you a build variant, for example productionDebug or betaRelease. Gradle automatically generates these based on your specified flavors and build types. A minor challenge that creeps up is having more than one build variant on a device. One needs to change the applicationId for this. It can be done in several ways, here’s one:

android {
//…
applicationVariants.all { variant ->
configureVariantFields(variant)
}
}

…and the actual function is something along the lines of:

def configureVariantFields(variant) {
switch (variant.name) {
case "developmentDebug":
variant.getMergedFlavor().applicationId = "com.org.app.development"
break;
}

So, this will work. The approach assumes that you have really specific configurations for each variant, and so the function configureVariantFields may be rather large. It’s worth mentioning that we had Instant Run enabled all of this time — it saved us a lot of time in development. So, what went wrong? Truthfully, not 100% sure, but it happened after we upgraded Gradle, Android build tools, and the target SDK.

Instant Run

Thanks to the threads found here, here, here and here, we were able to figure out that something with Instant Run was causing the issue. It seemed that the component was stricter than before when it came to applicationIds. A good article on what exactly Instant Run is can be found here.

The applicationId format used in the development product flavor was slightly different from the format used in the other flavors. The general convention is:

com.org.application.suffix

For us, the application portion was the problematic difference. In addition to this, some flavors had a suffix, others didn’t. In addition to THAT, we didn’t just append a suffix to the ID when required, we re-assigned the value via variant.getMergedFlavor().applicationId So, how did we fix this? Well, we started following the conventions and the applicationIds became:

com.org.app.development
com.org.app.alpha
com.org.app.beta
com.org.app

We took com.org.app to be the base in the android block:

android {
applicationId "com.org.app"
}

and then:

productFlavors {
development {
dimension "appType"
applicationIdSuffix ".development"
}
alpha {
dimension "appType"
applicationIdSuffix ".alpha"
}
beta {
dimension "appType"
applicationIdSuffix ".beta"
}
production {
dimension "appType"
}
}

Although the approach we took when naming the variants did bite us in the butts (for around a day — a Friday), the initial decision to have the names as they were made sense when the project was started about 1.5 years ago.

Bonus

So fun fact, being the crazy person that I am, I actually rooted one of the development phones for this. I wanted to see if using chmod 775 on the relevant /data/app directory would allow me to remove the folder at build time and fix this. After factory resetting the now-rooted device and STILL getting the error, I figured a permissions problem/remnant folder was not the problem. 😅

If you’ve made it this far — thanks for reading! Give this a clap, like, subscribe, share, make a node module out of it….Questions and suggestions are welcome in the comments!

--

--

Ivan Melnikov

Android Software Engineer @ Google. CS and Nuclear Engineering by education, one is just…way less explosive.