Android - Having Provider authority in the app project
Asked Answered
M

2

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?

Mick answered 28/5, 2012 at 22:11 Comment(1)
This may be a duplicate of #10321189, but nobody has answered so far.Mick
W
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

Wesla answered 28/5, 2012 at 22:39 Comment(11)
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... :PCzarina
@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.Wesla
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.Mick
@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.Wesla
@Wesla : Thanks for taking time on this.Mick
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.Urias
I wonder what URIwould you use in call() on ContentResolver to supply the authority to the ContentProvider.Nowt
@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.Wesla
@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.Nowt
@DraškoKokić: You cannot change an authority of a ContentProvider at runtime. My apologies if I misunderstood your original comment in this comment chain.Wesla
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 workBurly
Q
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

Quarry answered 29/1, 2013 at 20:56 Comment(3)
Close to the solution I implemented. Thanks for sharing!!Mick
@ Ian Warwick Actually i new to Android i working with Android Studio for me the same authorities problem can u plz help me by some samplesGwynethgwynne
This solution is simply awesome! Thank you very much! :DMizzen
W
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

Wesla answered 28/5, 2012 at 22:39 Comment(11)
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... :PCzarina
@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.Wesla
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.Mick
@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.Wesla
@Wesla : Thanks for taking time on this.Mick
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.Urias
I wonder what URIwould you use in call() on ContentResolver to supply the authority to the ContentProvider.Nowt
@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.Wesla
@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.Nowt
@DraškoKokić: You cannot change an authority of a ContentProvider at runtime. My apologies if I misunderstood your original comment in this comment chain.Wesla
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 workBurly

© 2022 - 2024 — McMap. All rights reserved.