None of existing answers satisfied me, however Liberty was close. So this is how am I doing it. First of all at the moment I am working with:
My goal is to run Debug
version along with Release
version on the same device using the same ContentProvider
.
In build.gradle of your app set suffix for Debug build:
buildTypes {
debug {
applicationIdSuffix ".debug"
}
}
In AndroidManifest.xml file set android:authorities
property of your ContentProvider
:
<provider
android:name="com.example.app.YourProvider"
android:authorities="${applicationId}.provider"
android:enabled="true"
android:exported="false" >
</provider>
In your code set AUTHORITY
property that can be used wherever needed in your implementation:
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
Tip: Before it was BuildConfig.PACKAGE_NAME
Once again I will start with my current setup:
Basically, if you need to customise some values for different builds you can do it from the build.gradle file:
BuildConfig.java
classAs an alternative for resources, you can create separate buildType or flavour directories and override XMLs or values within them. However, I am not going to use it in example below.
In build.gradle file add the following:
defaultConfig {
resValue "string", "your_authorities", applicationId + '.provider'
resValue "string", "account_type", "your.syncadapter.type"
buildConfigField "String", "ACCOUNT_TYPE", '"your.syncadapter.type"'
}
buildTypes {
debug {
applicationIdSuffix ".debug"
resValue "string", "your_authorities", defaultConfig.applicationId + '.debug.provider'
resValue "string", "account_type", "your.syncadapter.type.debug"
buildConfigField "String", "ACCOUNT_TYPE", '"your.syncadapter.type.debug"'
}
}
You will see results in BuildConfig.java class
public static final String ACCOUNT_TYPE = "your.syncadapter.type.debug";
and in build/generated/res/generated/debug/values/generated.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Automatically generated file. DO NOT MODIFY -->
<!-- Values from default config. -->
<item name="account_type" type="string">your.syncadapter.type.debug</item>
<item name="authorities" type="string">com.example.app.provider</item>
</resources>
In your authenticator.xml use resource specified in build.gradle file
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_type"
android:icon="@drawable/ic_launcher"
android:smallIcon="@drawable/ic_launcher"
android:label="@string/app_name"
/>
In your syncadapter.xml use the same resource again and @string/authorities too
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="@string/authorities"
android:accountType="@string/account_type"
android:userVisible="true"
android:supportsUploading="false"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"
/>
Tip: autocompletion(Ctrl+Space) does not work for these generated resource so you have to type them manually
New Android build system tip: ContentProvider authority renaming
I guess all of you have heard of the new Android Gradle-based build system. Let's be honest, this new build system is a huge step forward compared to the previous one. It is not final yet (as of this writing, the latest version is 0.4.2) but you can already use it safely in most of your projects.
I've personnaly switched most of my project to this new build system and had some issues because of the lack of support in some particular situations. One of which is the support for ContentProvider authority renaming
The new Android built system lets you deal with different types of your app by simply modifying the package name at build time. One of the main advantage of this improvement is you can now have two different versions of your app installed on the same device at the same time. For instance:
android {
compileSdkVersion 17
buildToolsVersion "17.0.0"
defaultConfig {
packageName "com.cyrilmottier.android.app"
versionCode 1
versionName "1"
minSdkVersion 14 // Listen to +Jeff Gilfelt advices :)
targetSdkVersion 17
}
buildTypes {
debug {
packageNameSuffix ".debug"
versionNameSuffix "-debug"
}
}
}
Using such a Gradle configuration, you can assemble two different APKs :
• A debug APK with the com.cyrilmottier.android.app.debug package name • A release APK with the com.cyrilmottier.android.app package name
The only issue with that is you won't be able to install the two APKs at the same time if they both expose a ContentProvider with the same authorities. Pretty logically we need to rename the authority depending on the current build type … but this is not supported by the Gradle build system (yet? ... I'm sure it will be fixed soon). So here is a way to go:
First we need to move the provider Android manifest ContentProvider declaration to the appropriate build type. In order to do that we will simply have :
src/debug/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cyrilmottier.android.app"
android:versionCode="1"
android:versionName="1">
<application>
<provider
android:name=".provider.Provider1"
android:authorities="com.cyrilmottier.android.app.debug.provider"
android:exported="false" />
</application>
</manifest>
src/release/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cyrilmottier.android.app"
android:versionCode="1"
android:versionName="1">
<application>
<provider
android:name=".provider.Provider1"
android:authorities="com.cyrilmottier.android.app.provider"
android:exported="false" />
</application>
</manifest>
Make sure to remove the ContentProvider declaration from the AndroidManifest.xml in src/main/ because Gradle doesn't know how to merge ContentProviders having the same name but a different authority.
Finally we may need to access to the authority in the code. This can be done pretty easily using the BuildConfig file and the buildConfig method:
android {
// ...
final PROVIDER_DEBUG = "com.cyrilmottier.android.app.debug.provider"
final PROVIDER_RELEASE = "com.cyrilmottier.android.app.provider"
buildTypes {
debug {
// ...
buildConfigField "String", "PROVIDER_AUTHORITY", PROVIDER_DEBUG
}
release {
buildConfigField "String", "PROVIDER_AUTHORITY", PROVIDER_RELEASE
}
}
}
Thanks to this workaround you'll be able to use BuildConfig.PROVIDER_AUTHORITY in your ProviderContract and install two different versions of your app at the same time.
Originaly on Google+: https://plus.google.com/u/0/118417777153109946393/posts/EATUmhntaCQ
While Cyril's example works great if you only have a few build types, it quickly gets complicated if you have many build types and/or product flavors as you need to maintain lots of different AndroidManifest.xml's.
Our project consists of 3 different build types and 6 flavors totaling 18 build variants, so instead we added support for ".res-auto" in ContentProvider authorities, which expand to the current packagename and removes the need to maintain different AndroidManifest.xml
/**
* Version 1.1.
*
* Add support for installing multiple variants of the same app which have a
* content provider. Do this by overriding occurrences of ".res-auto" in
* android:authorities with the current package name (which should be unique)
*
* V1.0 : Initial version
* V1.1 : Support for ".res-auto" in strings added,
* eg. use "<string name="auth">.res-auto.path.to.provider</string>"
*
*/
def overrideProviderAuthority(buildVariant) {
def flavor = buildVariant.productFlavors.get(0).name
def buildType = buildVariant.buildType.name
def pathToManifest = "${buildDir}/manifests/${flavor}/${buildType}/AndroidManifest.xml"
def ns = new groovy.xml.Namespace("http://schemas.android.com/apk/res/android", "android")
def xml = new XmlParser().parse(pathToManifest)
def variantPackageName = xml.@package
// Update all content providers
xml.application.provider.each { provider ->
def newAuthorities = provider.attribute(ns.authorities).replaceAll('.res-auto', variantPackageName)
provider.attributes().put(ns.authorities, newAuthorities)
}
// Save modified AndroidManifest back into build dir
saveXML(pathToManifest, xml)
// Also make sure that all strings with ".res-auto" are expanded automagically
def pathToValues = "${buildDir}/res/all/${flavor}/${buildType}/values/values.xml"
xml = new XmlParser().parse(pathToValues)
xml.findAll{it.name() == 'string'}.each{item ->
if (!item.value().isEmpty() && item.value()[0].startsWith(".res-auto")) {
item.value()[0] = item.value()[0].replace(".res-auto", variantPackageName)
}
}
saveXML(pathToValues, xml)
}
def saveXML(pathToFile, xml) {
def writer = new FileWriter(pathToFile)
def printer = new XmlNodePrinter(new PrintWriter(writer))
printer.preserveWhitespace = true
printer.print(xml)
}
// Post processing of AndroidManifest.xml for supporting provider authorities
// across build variants.
android.applicationVariants.all { variant ->
variant.processManifest.doLast {
overrideProviderAuthority(variant)
}
}
Example code can be found here: https://gist.github.com/cmelchior/6988275
Since the plugin version 0.8.3 (actually 0.8.1 but it wasn't working properly) you can define resources within the build file so this could be a cleaner solution because you don't need to create strings files nor additional debug/release folders.
build.gradle
android {
buildTypes {
debug{
resValue "string", "authority", "com.yourpackage.debug.provider"
}
release {
resValue "string", "authority", "com.yourpackage.provider"
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourpackage"
android:versionCode="1"
android:versionName="1">
<application>
<provider
android:name=".provider.Provider1"
android:authorities="@string/authority"
android:exported="false" />
</application>
</manifest>
I don't know if anybody mention it. Actually after android gradle plugin 0.10+, the manifest merger will provide the official support for this function: http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger
In AndroidManifest.xml, you can use ${packageName} like this:
<provider
android:name=".provider.DatabasesProvider"
android:authorities="${packageName}.databasesprovider"
android:exported="true"
android:multiprocess="true" />
And in your build.gradle you can have:
productFlavors {
free {
packageName "org.pkg1"
}
pro {
packageName "org.pkg2"
}
}
See full example here: https://code.google.com/p/anymemo/source/browse/AndroidManifest.xml#152
and here: https://code.google.com/p/anymemo/source/browse/build.gradle#41
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With