Using build types in Gradle to run same app that uses ContentProvider on one device
Asked Answered
S

14

133

I have set up Gradle to add package name suffix to my debug app so I could have release version that I'm using and debug version on one phone. I was referencing this: http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

My build.gradle file looks like this:

...
android
{
    ...
    buildTypes
    {
        debug
        {
            packageNameSuffix ".debug"
            versionNameSuffix " debug"
        }
    }
}

Everything works fine until I start using a ContentProvider in my app. I get:

Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]

I understand that this happens because two apps (release and debug) are registering same ContentProvider authority.

I see one possibility to solve this. If I understand correctly, you should be able to specify different files to use when building. Then I should be able to put different authorities in different resource files (and from Manifest set authority as string resource) and tell Gradle to use different resource for debug build. Is that possible? If yes then any hints on how to achieve that would be awesome!

Or maybe it's possible to directly modify Manifest using Gradle? Any other solution on how to run same app with ContentProvider on one device is always welcome.

Schematic answered 27/5, 2013 at 16:58 Comment(1)
For those interested in tracking upstream support for this use case : AOSP bug report. The "official" current stance is to use the manifest overriding solution.Lookout
E
240

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:

  • Android Studio Beta 0.8.2
  • Gradle plugin 0.12.+
  • Gradle 1.12

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

That's it! It will work like a charm. Keep reading if you use SyncAdapter!


Update for SyncAdapter (14.11.2014)

Once again I will start with my current setup:

  • Android Studio Beta 0.9.2
  • Gradle plugin 0.14.1
  • Gradle 2.1

Basically, if you need to customise some values for different builds you can do it from the build.gradle file:

  • use buildConfigField to access it from the BuildConfig.java class
  • use resValue to access it from resources e.g. @string/your_value

As 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.

Example


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

Eyebright answered 20/7, 2014 at 11:34 Comment(17)
The best answer IMHO. Nice short and simple example.Begrime
Yes, that's the best workaround I've seen so far. Thank you very much for sharing! Still have another issue unrelated to this since I need to update an explicit Intent in a preferences.xml file to use the new package name. code.google.com/p/android/issues/detail?id=57460Determination
@BerndS I have posted a comment to your issue with solution. You need to understand that changing applicationId by replacing it or setting suffix does not affect java packages. It is just an identifier of your app and it's decoupled from the java packages. See my answer to another question #24178507Eyebright
@Loop Did you manage to replace the application id in all .java and .xml files? I still have some places in this project where it does not work for me.Arid
@JDD The only places where I did some changes are posted in my answer. I had a quick look at your repo and I don't see my approach being used.Eyebright
@Arid Manifest placeholders only work in AndroidManifest.xml files, not in other files, such as res/xml/sync_adapter.xml. For that you will need to extend the build script yourself. Check my answer for a complete walkthrough.Anchovy
@RobMeeuwisse I did some modifications here however I could not get the strings replaced in sync_adapter.xml, authenticator.xml, which I need to if I apply the ${applicationId} in AndroidManifest.xml.Arid
@Arid The modifications that you link to would work without any custom build script. If you want to use ${applicationId} placeholders for sync_adapter.xml, authenticator.xml you must customize your build.gradle script. I see that you already have done much in your build.gradle script so you are comfortable with the idea. Did you follow the instructions in my answer and did it still not work?Anchovy
BuildConfig.PACKAGE_NAME is deprecated, you can use BuildConfig.APPLICATION_ID instead. android-review.googlesource.com/#/c/101596Kaila
I have updated my answer with how-to for syncadapterEyebright
Thanks to RobMeeuwisse + Loop: I will try it out and come back to you after.Arid
@Loop Could you please change the values of the debug configuration to an example application id such as com.example.myapp.debug?Arid
@JDD I am not sure which part you meant and why change is needed? Could you be more specific, please?Eyebright
Rather than having a tip to not use the deprecated PACKAGE_NAME (which doesn't work at all with the version of gradle I've just downloaded), could you update code in the answer to use the current preferred APPLICATION_ID and mention PACKAGE_NAME in the tips perhaps?Hultin
@Loop - I am unable to get this to work. I have tried a bunch of things but end up with the exception java.lang.SecurityException: caller uid XXXX is different than the authenticator's uid. Any idea what might be happening. My account_type and authority are the same i.e the applicationId.Pires
@PratikMandrekar It would be best if you post a question on StackOverflow with your code and XMLs. Then place a link to that question here so I am notified.Eyebright
Word of advice for anyone using this answer - make sure the import statement for BuildConfig matches the package name of your app. I had the import statement for the support library and it took me a while to figure out why BuildConfig.APPLICATION_ID was outputting android.support.compat.Boulogne
A
39

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

Amphiboly answered 4/6, 2013 at 10:40 Comment(1)
For someone that can't run gradle, bacause sintaxy error. Here is the answer: #20678618Oliverolivera
G
23

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

Genitive answered 15/10, 2013 at 8:45 Comment(4)
I switched to using something very similar for my project as well, because I had the same issue with build flavors. This approach works very well for now.Schematic
FileWriter makes trouble on utf-8 files, at least on my Mac OS. I changed the related line to: def writer = new OutputStreamWriter(new FileOutputStream(pathToFile), "UTF-8")Dominican
This is really great, thank you! I've made a small change to prevent breakage with formatted strings. gist.github.com/paour/8475929Intendancy
This was very helpful, but I ran into a problem where it wouldn't build after a clean because there was no values.xml file in the build folder at the processManifest stage. That doesn't exist until the processResources stage, at which point it's too late to modify the manifest, so to replace .res-auto in both the manifest and values files, I think you'd need 2 functions, one called by variant.processManifest.doLast, the other called by variant.processResources.doLast.Marianelamariani
S
20

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>
Seely answered 14/3, 2014 at 10:37 Comment(2)
Beware, resource-based authorities only work on Android 2.2.1 and later: github.com/android/platform_frameworks_base/commit/…Intendancy
this is also very useful in the searchable.xml for android:searchSuggestAuthority, because there you cannot use ${applicationId}Theodoratheodore
S
13

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

Silma answered 8/5, 2014 at 22:50 Comment(3)
This is great news, but it doesn't seem like it's a complete solution in the case of <searchable> elements which need to reference the autority, since these are not part of the manifest (but existing merge strategies do work for these files, unlike the Manifest).Intendancy
You don't have to use flavors for this, it works with build types as well. Also, it would be nice to mention that you can use BuildConfig.PACKAGE_NAME to get a static reference to your package. This is useful for content providers where the authority needs to be known at runtime to query the content provider.Tumult
Should also be updated to use ${applicationId} instead of ${packageName} for android:authoritiesDetermination
A
8

Use ${applicationId} placeholders in xml and BuildConfig.APPLICATION_ID in code.

You will need to extend the build script to enable placeholders in xml files other than the manifest. You could use a source directory per build variant to provide different versions of the xml files but maintenance will become cumbersome very quickly.

AndroidManifest.xml

You can use the applicationId placeholder out of the box in the manifest. Declare your provider like this:

<provider
    android:name=".provider.DatabaseProvider"
    android:authorities="${applicationId}.DatabaseProvider"
    android:exported="false" />

Note the ${applicationId} bit. This is replaced at build time with the actual applicationId for the build variant that is being built.

In code

Your ContentProvider needs to construct the authority string in code. It can use the BuildConfig class.

public class DatabaseContract {
    /** The authority for the database provider */
    public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".DatabaseProvider";
    // ...
}

Note the BuildConfig.APPLICATION_ID bit. It is a generated class with the actual applicationId for the build variant being built.

res/xml/ files, e.g. syncadapter.xml, accountauthenticator.xml

If you want to use a Sync Adapter you will need to provide meta-data for the ContentProvider and AccountManager in xml files in the res/xml/ directory. The applicationId placeholder is not supported here. But you can extend the build script yourself to hack it in.

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="${applicationId}"
    android:allowParallelSyncs="false"
    android:contentAuthority="${applicationId}.DatabaseProvider"
    android:isAlwaysSyncable="true"
    android:supportsUploading="true"
    android:userVisible="true" />

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="${applicationId}"
    android:icon="@drawable/ic_launcher"
    android:label="@string/account_authenticator_label"
    android:smallIcon="@drawable/ic_launcher" />

Again, note the ${applicationId}. This only works if you add the below gradle script to the root of your module and apply it from build.gradle.

build.gradle

Apply the extra build script from the module build.gradle script. A good place is below the Android gradle plugin.

apply plugin: 'com.android.application'
apply from: './build-processApplicationId.gradle'

android {
    compileSdkVersion 21
    // etc.

build-processApplicationId.gradle

Below is working source for a res/xml/ placeholder build script. A better documented version is available on github. Improvements and extensions are welcome.

def replace(File file, String target, String replacement) {
    def result = false;

    def reader = new FileReader(file)
    def lines = reader.readLines()
    reader.close()

    def writer = new FileWriter(file)
    lines.each { line ->
        String replacedLine = line.replace(target, replacement)
        writer.write(replacedLine)
        writer.write("\n")
        result = result || !replacedLine.equals(line)
    }
    writer.close()

    return result
}

def processXmlFile(File file, String applicationId) {
    if (replace(file, "\${applicationId}", applicationId)) {
        logger.info("Processed \${applicationId} in $file")
    }
}

def processXmlDir(File dir, String applicationId) {
    dir.list().each { entry ->
        File file = new File(dir, entry)
        if (file.isFile()) {
            processXmlFile(file, applicationId)
        }
    }
}

android.applicationVariants.all { variant ->
    variant.mergeResources.doLast {
        def applicationId = variant.mergedFlavor.applicationId + (variant.buildType.applicationIdSuffix == null ? "" : variant.buildType.applicationIdSuffix)
        def path = "${buildDir}/intermediates/res/${variant.dirName}/xml/"
        processXmlDir(new File(path), applicationId)
    }
}

Strings.xml

In my opinion there is no need to add placeholder support for resource strings. For the above use case at least it is not needed. However you could easily change the script to not only replace placeholders in the res/xml/ directory, but also in the res/values/ directory.

Anchovy answered 13/11, 2014 at 19:51 Comment(0)
R
6

I would rather prefer a mixture between Cyril and rciovati. I think is more simplier, you only have two modifications.

The build.gradle looks like:

android {
    ...
    productFlavors {
        production {
            packageName "package.name.production"
            resValue "string", "authority", "package.name.production.provider"
            buildConfigField "String", "AUTHORITY", "package.name.production.provider"
        }

        testing {
            packageName "package.name.debug"
            resValue "string", "authority", "package.name.debug.provider"
            buildConfigField "String", "AUTHORITY", "package.name.debug.provider"
        }
    }
    ...
}

And the AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="package.name" >

    <application
        ...>

        <provider android:name=".contentprovider.Provider" android:authorities="@string/authority" />

    </application>
</manifest>
Rabblerouser answered 25/7, 2014 at 10:33 Comment(0)
L
5

gradle.build

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.example.awsomeapp"
        minSdkVersion 9
        targetSdkVersion 23
        versionCode 1
        versionName "1.0.0"
    }

    productFlavors
    {
        prod {
            applicationId = "com.example.awsomeapp"
        }

        demo {
            applicationId = "com.example.awsomeapp.demo"
            versionName = defaultConfig.versionName + ".DEMO"
        }
    }

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

        debug {
            applicationIdSuffix ".debug"
            versionNameSuffix = ".DEBUG"
            debuggable true
        }
    }

    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // rename the apk
            def file = output.outputFile;
            def newName;
            newName = file.name.replace(".apk", "-" + defaultConfig.versionName + ".apk");
            newName = newName.replace(project.name, "awsomeapp");
            output.outputFile = new File(file.parent, newName);
        }

        //Generate values Content Authority and Account Type used in Sync Adapter, Content Provider, Authenticator
        def valueAccountType = applicationId + '.account'
        def valueContentAuthority = applicationId + '.authority'

        //generate fields in Resource string file generated.xml
        resValue "string", "content_authority", valueContentAuthority
        resValue "string", "account_type", valueAccountType

        //generate fields in BuildConfig class
        buildConfigField "String", "ACCOUNT_TYPE", '"'+valueAccountType+'"'
        buildConfigField "String", "CONTENT_AUTHORITY", '"'+valueContentAuthority+'"'

        //replace field ${valueContentAuthority} in AndroidManifest.xml
        mergedFlavor.manifestPlaceholders = [ valueContentAuthority: valueContentAuthority ]
    }
}

authenticator.xml

<?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:label="@string/app_name"
    android:smallIcon="@drawable/ic_launcher" />

sync_adapter.xml

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
              android:contentAuthority="@string/content_authority"
              android:accountType="@string/account_type"
              android:userVisible="true"
              android:allowParallelSyncs="false"
              android:isAlwaysSyncable="true"
              android:supportsUploading="true"/>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0.0" package="com.example.awsomeapp">

    <uses-permission android:name="android.permission.GET_ACCOUNTS"/><!-- SyncAdapter and GCM requires a Google account. -->
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>

    <!-- GCM Creates a custom permission so only this app can receive its messages. -->
    <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature"/>
    <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE"/>

    <application....
    .......

        <!-- Stub Authenticator --> 
        <service 
                android:name="com.example.awsomeapp.service.authenticator.CAuthenticatorService"
                android:exported="true">
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator"/>
            </intent-filter>
            <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator"/>
        </service>
        <!--  -->

        <!-- Sync Adapter -->
        <service
                android:name="com.example.awsomeapp.service.sync.CSyncService"
                android:exported="true"
                android:process=":sync">
            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>
            <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" />
        </service>
        <!--  -->

        <!-- Content Provider -->
        <provider android:authorities="${valueContentAuthority}"
            android:exported="false" 
            android:name="com.example.awsomeapp.database.contentprovider.CProvider">
        </provider>
        <!--  --> 
    </application>
</manifest>

Code:

public static final String CONTENT_AUTHORITY = BuildConfig.CONTENT_AUTHORITY;
public static final String ACCOUNT_TYPE = BuildConfig.ACCOUNT_TYPE;
Lexicographer answered 6/11, 2015 at 17:16 Comment(0)
I
4

Based on the sample by @ChristianMelchior, here's my solution, which fixes two issues in the previous solutions:

  • solutions that change values.xml in the build directory cause a full rebuild of resources (including aapt of all drawables)

  • for an unknown reason, IntelliJ (and probably Android Studio) do not reliably process the resources, causing the build to contain un-replaced .res-auto provider authorities

This new solution does things more the Gradle way by creating a new task and allows for incremental builds by defining input and output files.

  1. create a file (in the example I put it in a variants directory), formatted like a resource xml file, which contains string resources. These will be merged into the app's resources, and any occurrence of .res-auto in the values will be replaced with the variant's package name, for example <string name="search_provider">.res-auto.MySearchProvider</string>

  2. add the build_extras.gradle file from this gist to your project and reference it from the main build.gradle by adding apply from: './build_extras.gradle' somewhere above the android block

  3. make sure you set a default package name by adding it to the android.defaultConfig block of build.gradle

  4. in AndroidManifest.xml and other configuration files (such as xml/searchable.xml for auto-completion search providers), reference the provider (for example @string/search_provider)

  5. if you need to get the same name, you can use the BuildConfig.PACKAGE_NAME variable, for example BuildConfig.PACKAGE_NAME + ".MySearchProvider"

https://gist.github.com/paour/9189462


Update: this method only works on Android 2.2.1 and later. For earlier platforms, see this answer, which has its own set of problems, since the new manifest merger is still very rough around the edges…
Intendancy answered 24/2, 2014 at 15:15 Comment(3)
Where are you putting your variants directory? I have one large Android Studio project that depends on several Android modules--my main app and several Android Library modules. I can build from the command line, but when I try to build from inside Android Studio it looks for variants/res-auto-values.xml relative to /Applications/Android Studio.app/bin/. i.e. I get no FileNotFoundException for /Applications/Android Studio.app/bin/variants/res-auto-values.xml. I'm running on a mac. This is a great solution, but I'd love to get it working in the IDE for the other members of the team.Hasseman
Fixed my own problem. Gradle appears to resolve paths using System.getProperty("user.dir"), which returns a different result when invoked by the Android Studio build. The solution is to use the path relative to the project directory, which is returned with gradle.startParameter.getProjectDir(). See my comment in Paour's linked gist as well.Hasseman
Beware, resource-based authorities only work on Android 2.2.1 and later: github.com/android/platform_frameworks_base/commit/…Intendancy
S
3

I've written a blogpost with Github sample project that tackles this problem (and other similar problems) in a slightly different way than Cyril's.

http://brad-android.blogspot.com/2013/08/android-gradle-building-unique-build.html

Sofko answered 27/8, 2013 at 18:57 Comment(0)
M
2

Unfortunately, the current version (0.4.1) of the android plugin doesn't seem to provide a good solution for this. I haven't had time to try this yet, but a possible workaround for this problem would be to use a string resource @string/provider_authority, and use that in the manifest: android:authority="@string/provider_authority". You then have a res/values/provider.xml in the res folder of each build type that should override the authority, in your case this would be src/debug/res

I've looked into generating the xml file on the fly, but again, there doesn't seem to be any good hooks for it in the current version of the plugin. I'd recommend putting in a feature request though, I can imagine more people will run into this same issue.

Mckamey answered 28/5, 2013 at 12:42 Comment(1)
Hi Marcus, thanks for your reply. Your suggested solution is the only one I can think of myself for now. But my problem is, I don't know how to achieve that with Gradle.Schematic
R
2

The answer in this post works for me.

http://www.kevinrschultz.com/blog/2014/03/23/using-android-content-providers-with-multiple-package-names/

I use 3 different flavours so I create 3 manifest with content provider in each flavour as kevinrschultz said:

productFlavors {
    free {
        packageName "your.package.name.free"
    }

    paid {
        packageName "your.package.name.paid"
    }

    other {
        packageName "your.package.name.other"
    }
}

Your main Manifest not include providers:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- Permissions -->
<application>
    <!-- Nothing about Content Providers at all -->
    <!-- Activities -->
    ...
    <!-- Services -->
    ...
</application>

And your manifest in your each flavour including provider.

Free:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
    <!-- Content Providers -->
    <provider
        android:name="your.package.name.Provider"
        android:authorities="your.package.name.free"
        android:exported="false" >
    </provider>
</application>
</manifest>

Paid:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
    <!-- Content Providers -->
    <provider
        android:name="your.package.name.Provider"
        android:authorities="your.package.name.paid"
        android:exported="false" >
    </provider>
</application>
</manifest>

Other:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<application>
    <!-- Content Providers -->
    <provider
        android:name="your.package.name.Provider"
        android:authorities="your.package.name.other"
        android:exported="false" >
    </provider>
</application>
</manifest>
Resourceful answered 5/12, 2014 at 10:18 Comment(0)
C
0

Why not just add this?

type.packageNameSuffix = ".$type.name"

Colotomy answered 14/2, 2014 at 12:40 Comment(0)
W
0

My solution is to use placeholder replacement in AndroidManifest.xml. It also handles packageNameSuffix attributes so you can have debug and release as well as any other custom builds on the same device.

applicationVariants.all { variant ->
    def flavor = variant.productFlavors.get(0)
    def buildType = variant.buildType
    variant.processManifest.doLast {
        println '################# Adding Package Names to Manifest #######################'
        replaceInManifest(variant,
            'PACKAGE_NAME',
            [flavor.packageName, buildType.packageNameSuffix].findAll().join()) // ignores null
    }
}

def replaceInManifest(variant, fromString, toString) {
    def flavor = variant.productFlavors.get(0)
    def buildtype = variant.buildType
    def manifestFile = "$buildDir/manifests/${flavor.name}/${buildtype.name}/AndroidManifest.xml"
    def updatedContent = new File(manifestFile).getText('UTF-8').replaceAll(fromString, toString)
    new File(manifestFile).write(updatedContent, 'UTF-8')
}

I have it up on a gist too if you want to see if it evolves later.

I found to be a more elegant approach than the multiple resources and XML parsing approaches.

Whitewall answered 19/3, 2014 at 21:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.