Kevin Schultz

Mobile Engineering

Using Android Content Providers With Multiple Package Names

| Comments

One of the main benefits of switching to Gradle for your Android build system is the ability to create multiple packages from a single commit. This enables installing debug & release versions of the same app on a single phone, as well as more complicated flavor setups such as free/paid versions. Although setting up the actual package naming is simple, unfortunately some Android systems breakdown when the package name varies. One common problem is INSTALL_FAILED_CONFLICTING_PROVIDER errors when your app includes Content Providers.

Content Providers are accessed via Content URIs. The first portion of the Content URI is the Content Authority, which should correspond with the package name of the app. The Content URI has to match in both the Manifest and the definition of the Content Provider’s authority in Java. A typical implementation of a Content Provider’s manifest & Content URI definition for an app with a constant package name is below. (For a complete example of implementing a Content Provider I recommend Wolfram Rittmeyer’s post.)

Content Provider Contract
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * Defines contract between content providers and content consumers. A contract defines the
 * information that a client needs to access the provider as one or more data tables. A contract
 * is a public, non-extendable (final) class that contains constants defining column names and
 * URIs.
 */
public final class PlacesDB {

    /**
     * URI Matcher authority, has to be added to manifest identically in order
     * for content resolver to find the content provider
     */
    public static final String AUTHORITY = "com.kevinrschultz.placesapp.provider.places";

    private static final String SCHEME = "content://";

    public static final class Place implements BaseColumns {

        // URI definitions

        private static final String PATH_PLACES = "/places";

        private static final String PATH_PLACE_ID = "/places/";

        /** The content:// style URL for this table */
        public static final Uri CONTENT_URI = Uri.parse(SCHEME + PlacesDB.AUTHORITY + PATH_PLACES);

        /**
         * The content URI base for a single place. Callers must
         * append a numeric place id to this Uri to retrieve a place
         */
        public static final Uri CONTENT_ID_URI_BASE = Uri.parse(SCHEME + PlacesDB.AUTHORITY + PATH_PLACE_ID);

        /**
         * The content URI match pattern for a single place, specified by its ID. Use this to match
         * incoming URIs or to construct an Intent.
         */
        public static final Uri CONTENT_ID_URI_PATTERN = Uri.parse(SCHEME + PlacesDB.AUTHORITY + PATH_PLACE_ID + "/#");

        ...

    }
}
src/main/AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- Permissions -->
    <application>
        <!-- Content Providers -->
        <provider
            android:name="com.kevinrschultz.placesapp.model.PlacesProvider"
            android:authorities=com.kevinrschultz.placesapp.provider.places"
            android:exported="false" >
            <grant-uri-permission android:pathPattern=".*" />
        </provider>
        <!-- Activities -->
        ...
        <!-- Services -->
        ...
    </application>
</manifest>

Generating multiple packages from Gradle is relatively straightforward. You can either specify the entire package name for a particular build variant or use a packageNameSuffix. The example below specifies the complete package name, but in practice I prefer setting the root of the package name in the manifest and then appending build variant specific suffixes. This example shows splitting the packages based on build flavor (free v paid), though this applies equally to splitting the packages based on build type (debug vs release), or some combination of both.

build.gradle
1
2
3
4
5
6
7
8
9
productFlavors {
        free {
            packageName "com.kevinrschultz.placesapp.free"
        }

        paid {
            packageName "com.kevinrschultz.placesapp.paid"
        }
    }

No matter how you decide to split the packages, setting up the Content Provider Authority is the same. Since the release of Gradle Plugin 0.7.0 the package name is automatically added to the BuildConfig file and is available for use in constants.

Content Provider Contract Authority
1
    public static final String AUTHORITY = BuildConfig.PACKAGE_NAME + ".provider.places";

If you are working with several build variants, it is important to understand how Gradle merges the various sources sets. For Manifests, the XML from main, buildType, and buildFlavor are combined. This means that you do not have to duplicate your entire manifest into each build variant folder. Leave the constant portion in src/main, and then only extract the variable portion to the build variant specific folders. The example below is for splitting the package based on build flavor (free/paid), but would also apply for splitting based on build type (debug/release). In this case, nothing about the Content Providers is in the debug & release specific Manifests. Note that you still must put the Content Provider definition within the application block in the build variant specific manifests.

The actual definition will have an authority derived from the package name, but the Content Provider class reference does not change. When you change the package name for your app it effects the .apk’s ‘package name’, but it doesn’t actually change the Java packages at all.

src/main/AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- Permissions -->
    <application>
        <!-- Nothing about Content Providers at all -->
        <!-- Activities -->
        ...
        <!-- Services -->
        ...
    </application>
</manifest>
src/free/AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
    <application>
        <!-- Content Providers -->
        <provider
            android:name="com.kevinrschultz.placesapp.model.PlacesProvider"
            android:authorities=com.kevinrschultz.placesapp.free.provider.places"
            android:exported="false" >
            <grant-uri-permission android:pathPattern=".*" />
        </provider>
    </application>
</manifest>
src/paid/AndroidManifest.xml
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
    <application>
        <!-- Content Providers -->
        <provider
            android:name="com.kevinrschultz.placesapp.model.PlacesProvider"
            android:authorities=com.kevinrschultz.placesapp.paid.provider.places"
            android:exported="false" >
            <grant-uri-permission android:pathPattern=".*" />
        </provider>
    </application>
</manifest>

You can inspect the merged manifests in build/manifests/free/debug/AndroidManifest.xml and build/manifests/paid/debug/AndroidManifest.xml. If everything is setup correctly, the final merged manifest for any particular build variant will look almost exactly like the original single manifest, except with package name specific Content Authorities. This will allow you to run both apps on a single phone at one time.

Comments