Multiple Apps use same content provider
Asked Answered
S

5

41

I am developing a set of apps that are distinguished only in certain brandings (think different sports teams); however, I am running into a problem where I am using one Library project for all of the specifically branded apps and want to use the same ContentProvider for all of them. When I created the ContentProvider I declared the AUTHORITY as a constant in the class (per the dev example code) and I am using the same authority in every specific app in the manifest files. It looks like I can't use the same authority across every app as I get this error when trying to install a second app (I install one branded one just fine but the second install):

WARN/PackageManager(66): Can't install because provider name com.xxx.Provider (in package com.xxx) is already used by com.zzz

I've tried several approaches but none of them seem to work. One idea that I haven't done yet, was to create a library jar and just omit the Provider class I have and customize it in each specific app. Any ideas on how to get around this problem without resorting to that?

Salina answered 22/7, 2010 at 7:28 Comment(1)
You can try to apply my solution for similar task: https://mcmap.net/q/393070/-android-manifest-with-string-reference-specifically-android-authoritiesGratt
D
22

ContentProviders are identified by the authority, so it needs to be unique. I don't think there are any tricks around that.

Additionally, there's a bug in the Android platform that also prevents using the same classname for two different ContentProviders, even if they have different authority and are contained in separate APKs. See the bug here.

The solution I advise for you is to create the abstract provider class in your library project, and then extend it with a unique name in each of the individual applications. To make this practical you will probably need to create a script to generate/modify the individual manifests and contentprovider classes.

Hope this helps.

Dreamy answered 27/5, 2011 at 21:6 Comment(3)
Whenever you want to extend the android basic feature set and you assume it will work (as its intuitive), you come to know it doesn't.Lineage
This answer is no longer true with newer version of Android, because Google resolved the issue in 2014 as stated hereBanish
Don't create script, use productFlavors: developer.android.com/studio/build/build-variantsDotard
D
26

It's an old question, but I was looking at doing something similar recently. With the Build flavours, its really straight forward now.

Specify the BuildConfigField in the gradle file:

    productFlavors {
    free {
        applicationId "com.example.free"
        buildConfigField 'String', 'AUTHORITY', '"com.example.free.contentprovider"'
    }

    paid {
        applicationId "com.example.paid"
        buildConfigField 'String', 'AUTHORITY', '"com.example.paid.contentprovider"'
    }

Specify the provider authority in the manifest:

    <provider
        android:name=".ContentProvider"
        android:authorities="${applicationId}.contentprovider" />

Set the authority in the provider using the BuildConfigField Variable:

    public static final String AUTHORITY = BuildConfig.AUTHORITY
Dhumma answered 20/8, 2016 at 22:32 Comment(2)
This should be the new accepted answer. Although you can avoid creating the buildConfigField by using getApplicationContext().getPackageName() at runtime instead.Swot
I used BuildConfig.APPLICATION_ID for this purpose - I didn't have app context available in content provider.Watchdog
D
22

ContentProviders are identified by the authority, so it needs to be unique. I don't think there are any tricks around that.

Additionally, there's a bug in the Android platform that also prevents using the same classname for two different ContentProviders, even if they have different authority and are contained in separate APKs. See the bug here.

The solution I advise for you is to create the abstract provider class in your library project, and then extend it with a unique name in each of the individual applications. To make this practical you will probably need to create a script to generate/modify the individual manifests and contentprovider classes.

Hope this helps.

Dreamy answered 27/5, 2011 at 21:6 Comment(3)
Whenever you want to extend the android basic feature set and you assume it will work (as its intuitive), you come to know it doesn't.Lineage
This answer is no longer true with newer version of Android, because Google resolved the issue in 2014 as stated hereBanish
Don't create script, use productFlavors: developer.android.com/studio/build/build-variantsDotard
G
6

YOU CAN!

As said in this post (wich explains how Firebase initializes its library without giving it a context from your Application#onCreate() method), you can use a placeholder in your manifest, like this:

    <provider
         android:authorities="${applicationId}.yourcontentprovider"
         android:name=".YourContentProvider" />
Gabi answered 31/1, 2017 at 18:11 Comment(1)
Actually... too short, doesn't work unless you know what you're doing. I'd suggest this answer: https://mcmap.net/q/341735/-possible-to-use-multiple-authorities-with-fileproviderHorologe
W
4

Lets say your library package is com.android.app.library free package is com.android.app.free paid package is com.android.app.paid

In your free project and paid project, make an identical file in a package which can be anything, but must be the same.

Example:

  1. Create a new package in your free version with com.android.app.data

  2. Create a file called Authority.java and inside (Authority.java) put:

    public class Authority {

    `public static final String CONTENT_AUTHORITY = "YOUR PROVIDER";`
    

    }

  3. Repeat this for the paid version, remember to keep the package name the same and class name.

Now, in your contract file, in your library use the following:

public static String AUTHORITY = initAuthority();

    private static String initAuthority() {
        String authority = "something.went.wrong.if.this.is.used";

        try {

            ClassLoader loader = Contract.class.getClassLoader();

            Class<?> clz = loader.loadClass("com.android.app.data.Authority");
            Field declaredField = clz.getDeclaredField("CONTENT_AUTHORITY");

            authority = declaredField.get(null).toString();
        } catch (ClassNotFoundException e) {} 
        catch (NoSuchFieldException e) {} 
        catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }

        return authority;
    }

    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);

Now you should be able to use two authorities.

Credit: Ian Warick (code write up) Android - Having Provider authority in the app project Disclaimer: I posted it here as well: Android duplicate provider authority problem - Not sure if allowed to answer the same type of question with the same answer.

Woodman answered 12/1, 2014 at 0:41 Comment(0)
M
1

The following way can be used to package a ContentProvider within a library and set the ContentProvider's authority at runtime, so that it can be included into multiple projects without ContentProvider Authority conflict. This works because the real 'authority' comes from the AndroidManifest...not the ContentProvider class.

Start with the basic ContentProvider implementation..AUTHORITY, CONTENT_URI and UriMatcher are static, but not 'final'....

public class MyContentProvider extends ContentProvider {
    public static String  AUTHORITY = "com.foo.bar.content";
    public static Uri     CONTENT_URI = Uri.parse("content://" + AUTHORITY);
    protected static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

Then, override the 'attachInfo' method, so that when the ContentProvider is first initialized, your ContentProvider will be called with the ProviderInfo that's gleaned from the AndroidManifest. This will occur BEFORE any possible queries are made, most likely during the initial Application class setup. Use this opportunity to reset the AUTHORITY, CONTENT_URI and UriMatcher to their 'real' values, as provided by the Application that's using the ContentProvider library.

    @Override
public void attachInfo(Context context, ProviderInfo info) {
    super.attachInfo(context, info);
    AUTHORITY = info.authority;
    CONTENT_URI = Uri.parse("content://" + AUTHORITY);
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(AUTHORITY, AlarmTable.TABLENAME, ALARMS);
    uriMatcher.addURI(AUTHORITY, AttributeTable.TABLENAME, ATTRIBUTES);
    uriMatcher.addURI(AUTHORITY, DeepLinkTable.TABLENAME, DEEPLINKS);
    uriMatcher.addURI(AUTHORITY, NotificationTable.TABLENAME, NOTIFICATIONS);
    uriMatcher.addURI(AUTHORITY, MetaDataTable.TABLENAME, RESOURCE_METADATA);
    uriMatcher.addURI(AUTHORITY, ResourceTable.TABLENAME, RESOURCES);
    uriMatcher.addURI(AUTHORITY, ResourceAttributeTable.TABLENAME, RESOURCES_ATTRIBUTES);
    uriMatcher.addURI(AUTHORITY, ResourceTagTable.TABLENAME, RESOURCES_TAGS);
    uriMatcher.addURI(AUTHORITY, TagTable.TABLENAME, TAGS);
    uriMatcher.addURI(AUTHORITY, UserTagTable.TABLENAME, USER_TAGS);
    uriMatcher.addURI(AUTHORITY, UserTable.TABLENAME, USERS);
    uriMatcher.addURI(AUTHORITY, CUSTOM, RAW);
}

When the Application is started, the ContentProvider is actually instantiated along with the Application class, so it will have access to all the required package info. the ProviderInfo object will contain the information provided in the AndroidManifest... The listing that's included in the final Application.

        <provider android:authorities="com.foo.barapp.content"
              android:name="com.foo.bar.MyContentProvider"/>

The Authority will now be rewritten with "com.foo.barapp.content" instead of the default value, and the UriMatcher will be updated to the application's value instead of the default. Classes that rely on the "AUTHORITY" will now access the updated value, and the UriMatcher will properly distinguish the incoming queries for the 'com.foo.barapp.content'.

I've tested this with both both a sample application and an androidTest package simultaneously and found it to work correctly.

Messenger answered 9/8, 2016 at 18:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.