Code-Apprentice Code-Apprentice - 3 months ago 50
Android Question

Dynamically set the authority of a ContentProvider

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?

Answer

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).
Comments