Android: How to Implement ProductFlavor-Dependent Permissions with Gradle

A few months ago we were working on a client app project, which releases many different brands from the same core project. In order to keep the code base simple and easy to work with, we're using one android project with one productFlavor for each brand. Some of these brands received custom features, which require additional permissions. In this blog post, we'll show you how to set up productFlavor-dependent permissions.

  1. Android: How to Implement ProductFlavor-Dependent Permissions with Gradle
  2. Android: How to Implement ProductFlavor-Dependent Dependencies with Gradle

Requirements

Before you get excited, there are a few requirements:

  • You must be using the Gradle build system
  • You must have one code base/project with multiple ProductFlavors
  • The different product flavors are requiring different sets of permissions (otherwise it doesn't make sense).

How to Setup ProductFlavor-Dependent Permissions

Let's look at a simple example of having an app, which has a basic free, a paid premium and an full-fledged enterprise version of the app. The premium and enterprise versions each add additional functionality, for example location tracking. That isn't available for the free users, so the request for location permissions is unnecessary and might deter users from installing your app. In the long run this could hurt your sales and revenue.

For the mentioned example, the build.gradle would look like this:

android {

    ...

    productFlavors {
        free {
            applicationId "io.futurestud.awesomeapp.free"
            versionCode 1
            versionName "1.0.0"
        }

        premium {
            applicationId "io.futurestud.awesomeapp.premium"
            versionCode 1
            versionName "1.0.0"
        }

        enterprise {
            applicationId "io.futurestud.awesomeapp.enterprise"
            versionCode 1
            versionName "1.0.0"
       }
   }

   ....

}

Simple Implementation

Everyone with some good knowledge of the Gradle build system should be aware of the simple solution. The permissions are defined in the AndroidManifest.xml. Of course, you could setup a completely new AndroidManifest.xml for each product flavor. Your setup would be:

  • app/src/free/AndroidManifest.xml
  • app/src/premium/AndroidManifest.xml
  • app/src/enterprise/AndroidManifest.xml

In each AndroidManifest.xml you'd list the permissions required for that specific product flavor. Now your apps will only have to ask the users for permissions they really need.

The issue with this solution is duplicated code. The AndroidManifest.xml does not only contain the permissions, but also information about Activities, Services, BroadcastReceivers etc. This leads to a messier code base and possible headaches, if you're maintaining the project in the future.

Better Implementation

An alternative solution we're using is to only move the permissions into the product-flavor-dependent AndroidManifest.xmls and leave the Activities and all other common information in one core AndroidManifest.xml.

The improved setup would be:

  • app/src/main/AndroidManifest.xml contains common information about Activities, … but no permissions :
<manifest  
    xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- permissions are imported via brand-specific AndroidManifest.xml -->

    <!-- common manifest elements -->
    <application
        android:name=".ui.FSApplication"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">

        <!-- activities -->
        <activity
            android:name=".ui.activities.SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity
            android:name=".ui.activities.SecondActivity/>

        <!-- receiver ->
        <receiver android:name=".receiver.BlogPostReceiver"/>

    </application>
</manifest>  
  • app/src/free/AndroidManifest.xml contains only permissions for the free product flavor
<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android">  
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>  
  • app/src/premium/AndroidManifest.xml contains only permissions for the premium product flavor
<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android">  
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>  
  • app/src/enterprise/AndroidManifest.xml contains only permissions for the enterprise product flavor.
<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android">  
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>  

The advantage here is that you only have one place (the /main/AndroidManifest.xml) for the information all product flavors have in common. The specialized information, for example permissions, go into the /<productflavor>/AndroidManifest.xml.

That's it! You won't have to do anything more! Gradle will automatically merge the /main manifest with the selected product flavor mainfest and you've product-flavor-dependent permissions.

Explore the Library

Find interesting tutorials and solutions for your problems.