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.
Related Posts
- Android: How to Implement ProductFlavor-Dependent Permissions with Gradle
- 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.xml
s 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.