Building ContentProviders for apps with many flavors

I work on an app that supports a few different Android build types: dev, debug, and release. I want to share what I learned today about properly labeling ContentProviders so that they work with each of these flavors.

We use multiple build variants so that we can install several different versions of the same app on the device. A release-signed copy of the production app can be installed to the same place as the dev .apk I just built.

# app/build.gradle
android {
    ...
    buildTypes {
    dev {
        debuggable true
        // no minifier enabled
        versionNameSuffix ".debug"
        applicationIdSuffix ".debug"
        signingConfig signingConfigs.debug
    }

    debug {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        versionNameSuffix ".debug"
        applicationIdSuffix ".debug"
    }

    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        signingConfig signingConfigs.release
    }
}

The dev configuration is a homegrown variant that skips ProGuard's code minification step. This allows us to run the debugger in Android Studio when building with dev. (Among things I learned the other day: breakpoints don't work on minified code!)

Other than that, we're implementing the debug and release variants that come with the Android Gradle plugin. So far, so good...

Conflict with ContentProvider

Recently we added a ContentProvider to help make queries with ActiveAndroid. That ContentProvider is duly named in the manifest:

# AndroidManifest.xml
...
<provider android:name="com.activeandroid.content.ContentProvider" 
android:authorities="com.x.y.debug"
android:exported="false" />

And then we realized that we could no longer install more than one variant of the app on any device. If a debug version happened to be installed, then upgrading the production app from the Play Store would break with a mysterious '-505' error. Meanwhile, trying to install a debug version next to prod caused this error in Android Studio:

Installing com.x.y.debug
DEVICE SHELL COMMAND: pm install -r       "/data/local/tmp/com.x.y.debug"
pkg: /data/local/tmp/com.x.y.debug
Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

It shows up with red text in Android Studio, too. Ugh. Happily, there's this StackOverflow post about it.

So, the ContentProvider needs to be granted a unique authority name in the manifest for each of the different .apks that we build for this app. The authority name is that android:authorities string in the <provider> tag.

Untangling the provider name

How do you assign a different value based on the build variant? Using the resValue function, you can define a custom resource string in build.gradle, and give it a different definition in each build variant. A resource string is one whose value you can access with @string/your_config variable_name.

Here's the app's build.gradle file again.

# app/build.gradle
android {
    ...
    buildTypes {
    dev {
        debuggable true
        // no minifier enabled
        versionNameSuffix ".debug"
        applicationIdSuffix ".debug"
        signingConfig signingConfigs.debug

        resValue "string", "content_provider", "com.x.y.debug.provider"
    }

    debug {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        versionNameSuffix ".debug"
        applicationIdSuffix ".debug"

        # debug and dev have the same apk signature, 
        # so they can share an authority name!
        resValue "string", "content_provider", "com.x.y.debug.provider"
    }

    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        signingConfig signingConfigs.release

        resValue "string", "content_provider", "com.x.y.provider"
    }
}

You'll need to build once in order for the new content_provider string to be compiled into a resource.

And, not least, here's that <provider> in the manifest:

# AndroidManifest.xml
<provider android:name="com.activeandroid.content.ContentProvider" 
android:authorities="@string/content_provider"
android:exported="false" />

One ContentProvider, two builds, installed side-by-side, just like you want.