17

An android library project contains a few providers whose authority is defined like the following in a contract class :

public static final String CONTENT_AUTHORITY = "my.com.library.providers.tester";
private static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

Now there are a lot of app projects which uses this library project. The problem I am currently having is that for every app project, I need to have a separate branch in the library project for every app just for having a unique content authority. This is creating some version management problems (like propagating features/bug fixes from one branch to every other branch etc.,). Instead I would like to delegate the responsibility of defining the content authority to the app project. Is there a way to accomplish this?

500865
  • 6,920
  • 7
  • 44
  • 87
  • 1
    This may be a duplicate of http://stackoverflow.com/questions/10320689/android-content-provider-in-library-project, but nobody has answered so far. – 500865 May 28 '12 at 22:12

2 Answers2

23

I know this is an old topic but came across this issue today and we have been developing for quite some time so was not ready to go through all statics in our Content Provider Contract and change them, also because our content provider and DB are generated by the Mechanoid Plugin for Eclipse (Yes, I am also the author! :))

The solution I came up with was to add a static initializer to our generated contract that uses reflection to look up a class and use a static CONTENT_AUTHORITY field on that if it exists, if not fall back to a default:

public class QuxContract  {
    public static final String CONTENT_AUTHORITY = initAuthority();

    private static String initAuthority() {
        String authority = "com.example.app.data.qux";

        try {

            ClassLoader loader = QuxContract.class.getClassLoader();

            Class<?> clz = loader.loadClass("com.example.app.data.QuxContentProviderAuthority");
            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;
    }

    private static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
// ...

Now in each project that links to the library project can provide their own authority:

package com.example.app.data;

public class QuxContentProviderAuthority {
    public static final String CONTENT_AUTHORITY = "com.example.app.data.baz";
}

Also, do not forget to change the authority in your manifest also

Ian Warwick
  • 4,774
  • 3
  • 27
  • 27
22

The app is the only one that absolutely needs to know about the authority, as it is the one that declares the <provider> in the manifest with the android:authorities attribute.

Hence, in principle, it should "just work", so long as you remove all authority-specific logic from the provider, such as:

  • those static data members (which now move to the hosting app)
  • UriMatcher (roll something yourself that does not examine the authority, but focuses on the rest of the Uri)

If, for some reason, you are absolutely sure that your provider needs to know its authority, then the app will have to supply that to the provider before the provider is used for real work. Possible ways to do that include:

  • Since a ContentProvider is a natural singleton, assign it to a static data member, and then supply the authority string to it by a custom method from a custom Application class (as providers are initialized first, so this should work)

  • If you are only supporting API Level 11+, have the custom Application class use call() on ContentResolver to supply the authority to the ContentProvider

  • Assume that the only real calls (e.g., to query(), insert()) are valid, and just lazy-initialize your authority based on what comes in on the first Uri you see

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • I was almost done answering the question... then I saw "1 new answer" pop up, and of course it was CommonsWare, whose answer covered everything I mentioned and more. Just my luck... :P – Alex Lockwood May 28 '12 at 22:42
  • @AlexLockwood: Sorry about that. If it makes you feel better, it happens to me too. Also, don't be afraid to just go ahead and post your answer when this sort of thing happens. It's possible that your phrasing or something will resonate better with the questioner than does the existing answer. Besides, if you went through all the trouble to type in (most of) an answer, you may as well publish it. – CommonsWare May 28 '12 at 22:53
  • I get the idea. I was thinking about code modification during compile time. Isn't that a possibility? If that is possible it would be easy and probably simple than this approach. – 500865 May 28 '12 at 22:55
  • @500865: "Isn't that a possibility?" -- um, not readily, unless you're going to roll your own Ant task or something that makes a copy of the library, adjusts the source, adjusts the `project.properties` of the hosting app to point to the copy, etc. "If that is possible it would be easy and probably simple than this approach." -- I would think that code modification during compile time would be a few orders of magnitude more complicated. After all, lazy-finding your `Uri` based on the first `query()`, etc. call should be under 10 lines of code. – CommonsWare May 28 '12 at 23:01
  • @CommonsWare : Thanks for taking time on this. – 500865 May 28 '12 at 23:03
  • I solved that with an abstract method in the Application class 'getAuthority()' in a library project. Then it can be implemented in 2 different app projects having 2 different authorities. But later on I developed more complex approach - a 3rd app solely to host the content provider which is now shared between the client apps. – WindRider Feb 07 '14 at 11:35
  • I wonder what `URI`would you use in `call()` on `ContentResolver` to supply the authority to the `ContentProvider`. – Draško Kokić May 05 '14 at 09:24
  • @DraškoKokić: `content://your.authority.goes.here` should work. Add other segments if desired, though since `call()` is not passed the `Uri`, those segments will not be that useful. – CommonsWare May 05 '14 at 10:59
  • @CommonsWare: Would you supply the *new* authority in the URI or as an additional argument? What confuses me is that in order to invoke `call` on `ContentResolver` one needs to know the authority (the CP registered with android), so I am not able to see how this could be used to configure the authority afterwords. – Draško Kokić May 08 '14 at 12:59
  • @DraškoKokić: You cannot change an authority of a `ContentProvider` at runtime. My apologies if I misunderstood your original comment in this comment chain. – CommonsWare May 08 '14 at 13:03
  • 1
    None of the above mentioned solution worked for me: 1. Correct that ContentProvider is singleton but what if use ContentProvider in a library that has widget, now user will not open the app to put widget on screen so how ContentProvider authority will not get initialized with public static constant. 2. call method of ContentResolver I cannot used as suggested by @DraškoKokić: 3. What if am doing query() calls from library itself instead of application then in that case 3rd solution will also not work – Sagar Trehan Aug 20 '15 at 14:59