Dynamically set the authority of a ContentProvider
Asked Answered
T

3

10

Perhaps the title is a bit misleading. My problem is that I have an Android library project which is shared between two standard Android projects: one for a free version of the app and the other for a paid version. The library currently has the code for a ContentProvider, including a contract class with several static String variables for things such as the URI and column names. Now I want the "authority" for the URI to change depending on which app is using the library. One solution that comes to mind is storing the authority as a string resource and loading that string at run-time into the static final String variable. However, I'm not sure how to do this as the contract class has a private constructor and no Context object in order to load the string resource. What other options are available to solve my problem?

Trespass answered 28/4, 2013 at 21:30 Comment(2)
Do you care to check my answer and let me know if it answers your question or if there's something amiss?Aftonag
@EmanuelMoecklin I upboated your A but haven't had time to play with my app since I asked this Q. This is for a hobby app and work has been getting in the way ;-( I'll accept an answer once I have a chance to figure out which one is most helpful.Trespass
A
7

Using different authorities for the free and the paid version makes sense in case the user tries to install both versions. I'm defining a different authority for the two versions in the manifest like so:

<provider
    android:name="MyApp.MyProvider"
    android:authorities="MyApp.MyProvider.free"
    android:grantUriPermissions="true"/>

Then I configure the provider in an xml file (I use a special config.xml file because I have more configuration data like the provider authority, but you can use strings.xml of course):

<string name="my_provider_authority">MyApp.MyProvider.free</string>

The code retrieves the provider authority as any other string resource. To access string resources without a context use the application context. I'm using an application class to have access to the application context from anywhere in my app (there are two exceptions though):

public class MyApplication extends Application {
    private static Context sContext;

    @Override
    public void onCreate() {
        super.onCreate();
        sContext = this;
    }

    public static Context getContext() {
        return sContext;
    }
}

Of course you need to define MyApplication in your manifest. This allows you to access string and other resources from anywhere in your app. There are two exception though:

  1. ContentProviders. ContentProviders can be started before Application starts and so you won't have an Application context available. That's no problem though because ContentProviders get their own context through getContext().
  2. Static code: the context might not be available outside the life cycle of Android components (Activities, Fragments, BroadcastReceivers, Services etc.). Static initializers that are relying on the application context are therefore not a good idea. But that's also not a real issue because using a context outside the life cycle of Android components isn't allowed anyway and static methods accessing a context would always be called from within that life cycle. E.g. if an Activity needs to know a ContentProvider's authority it would call a static method in your contract class and that call would be from one of the activity's onXYZ() methods like onCreate() or onStart() which would make sure that the context is initialized. So all you need to do is lazy initialize the variables in your contract class and make sure the caller does retrieve the variables only when it's clear that Application.onCreate() has been called before. Of course from within an activity you could retrieve the string resources directly. The real advantage of my method will become obvious when you need the resources in other classes/objects. These objects would still be tied to the life cycle of some Android component but you wouldn't have to pass around the context to all these objects, which is 1) very cumbersome and 2) very error prone when it comes to leaking the context which could lead to memory usage issues (one of the most common problems with Android apps).
Aftonag answered 29/4, 2013 at 1:42 Comment(3)
Thanks for the suggestions. I think creating an Application subclass sounds like the way to go. What issues do I need to be aware of when I call MyApplication.getContext() from a static initializer in my "contract" class? Also, can you use @string/my_provider_authority in the android:authorities attribute in order to reduce duplication?Trespass
I modified my answer to address your concerns about using the application context in a static initializer. The answer is basically to lazy initialize the variables that read from a string resource and to make sure you don't call the getter methods from outside the application life cycle.Aftonag
According to this answer: https://mcmap.net/q/394281/-using-string-for-android-authorities-in-a-contentprovider/534471 you shouldn't use a @string reference to define the authority of a provider but you could (at least in Android version higher 2.1). But I agree with the answer there that it's dangerous and should probably be omitted.Aftonag
S
33

Here's a better solution for those using newer versions of the build tools: make the authority relative to your application ID. You can do this automatically using ${applicationId}, which is expanded into your app's application ID during the build process.

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

Let's say your application IDs are com.example.app.paid and com.example.app.free. When you build your app, the authority will become com.example.app.paid.provider and com.example.app.free.provider, correspondingly.

To reference the provider authority in your code, use BuildConfig.APPLICATION_ID + ".provider".

Subfloor answered 12/8, 2016 at 3:6 Comment(5)
Thanks for the update on the features provided by Android Studio and the gradle build chain.Trespass
In addition, how would I refer to the free app's provider from the paid app. This is one of the main reasons for the two different providers. When the user buys the paid app, I need to be able to import data from the free app.Trespass
@Trespass The standard practice is to make the paid upgrade an in-app purchase, so you can keep using the same provider. Alternatively, you can mark your provider as android:exported="true", which allows your provider data to be read by other apps (remember to add permissions so only your app can read it!)Subfloor
As you can see from the time stamp on this question, I have been working on this for a while. I found a solution that seemed to work at the time and I didn't know about in-app purchases or I would have probably gone that route. I found that providers can automatically be used by any app which is signed with the same key. Since I only want to use them between my own apps, I don't need to export the providers.Trespass
The issue at hand is having the name of the free app's provider available in paid app. Of course, I can hard code it, but it would be nice to have something slightly more dynamic. I have a "contract" class where I would like to have String constants with the provider names.Trespass
A
7

Using different authorities for the free and the paid version makes sense in case the user tries to install both versions. I'm defining a different authority for the two versions in the manifest like so:

<provider
    android:name="MyApp.MyProvider"
    android:authorities="MyApp.MyProvider.free"
    android:grantUriPermissions="true"/>

Then I configure the provider in an xml file (I use a special config.xml file because I have more configuration data like the provider authority, but you can use strings.xml of course):

<string name="my_provider_authority">MyApp.MyProvider.free</string>

The code retrieves the provider authority as any other string resource. To access string resources without a context use the application context. I'm using an application class to have access to the application context from anywhere in my app (there are two exceptions though):

public class MyApplication extends Application {
    private static Context sContext;

    @Override
    public void onCreate() {
        super.onCreate();
        sContext = this;
    }

    public static Context getContext() {
        return sContext;
    }
}

Of course you need to define MyApplication in your manifest. This allows you to access string and other resources from anywhere in your app. There are two exception though:

  1. ContentProviders. ContentProviders can be started before Application starts and so you won't have an Application context available. That's no problem though because ContentProviders get their own context through getContext().
  2. Static code: the context might not be available outside the life cycle of Android components (Activities, Fragments, BroadcastReceivers, Services etc.). Static initializers that are relying on the application context are therefore not a good idea. But that's also not a real issue because using a context outside the life cycle of Android components isn't allowed anyway and static methods accessing a context would always be called from within that life cycle. E.g. if an Activity needs to know a ContentProvider's authority it would call a static method in your contract class and that call would be from one of the activity's onXYZ() methods like onCreate() or onStart() which would make sure that the context is initialized. So all you need to do is lazy initialize the variables in your contract class and make sure the caller does retrieve the variables only when it's clear that Application.onCreate() has been called before. Of course from within an activity you could retrieve the string resources directly. The real advantage of my method will become obvious when you need the resources in other classes/objects. These objects would still be tied to the life cycle of some Android component but you wouldn't have to pass around the context to all these objects, which is 1) very cumbersome and 2) very error prone when it comes to leaking the context which could lead to memory usage issues (one of the most common problems with Android apps).
Aftonag answered 29/4, 2013 at 1:42 Comment(3)
Thanks for the suggestions. I think creating an Application subclass sounds like the way to go. What issues do I need to be aware of when I call MyApplication.getContext() from a static initializer in my "contract" class? Also, can you use @string/my_provider_authority in the android:authorities attribute in order to reduce duplication?Trespass
I modified my answer to address your concerns about using the application context in a static initializer. The answer is basically to lazy initialize the variables that read from a string resource and to make sure you don't call the getter methods from outside the application life cycle.Aftonag
According to this answer: https://mcmap.net/q/394281/-using-string-for-android-authorities-in-a-contentprovider/534471 you shouldn't use a @string reference to define the authority of a provider but you could (at least in Android version higher 2.1). But I agree with the answer there that it's dangerous and should probably be omitted.Aftonag
C
0

Why change the authority at all? You're not required to export the provider, which means that nobody could even see the authority name except by deconstructing the app. Even then, they wouldn't be able to access the provider.

If it's for your own internal convenience, then I'd use the same authority but put different security on the URIs.

In short, your idea is interesting, but I wouldn't do it that way. Too much of a mess.

Concurrent answered 28/4, 2013 at 22:29 Comment(3)
What would happen if a user has both versions installed?Trespass
The free version also exposes a read-only provider to allow the paid version to import existing data. Ultimately, the provider in both the free and paid versions will be used internally by a CursorLoader. If I understand correctly, I don't need a <provider> tag for the later use case since it is internal to each app.Trespass
My testing suggests that authorities have to be globally unique (on the phone) even if the provider isn't exported.Kyanize

© 2022 - 2024 — McMap. All rights reserved.