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.