Building Multiple Editions of an Android App with Gradle

A mobile application we’ve worked on recently had a special requirement. The app would be released multiple times, under different name, branding and minor feature differences. This can very easily turn into a mess quickly, if all the apps are not maintained in a single code base.

The way you would usually approach this is by building the whole app as an Android library project, and you would then build an Android application project for each edition. That can work, but you would need a lot of work to get the library/app separation to work, because your library is actually an application, and that could cause some confusions.

However, we were already migrating our Android development to Android Studio, and the new build system (Gradle) has a specific solution to this problem: meet Flavors.

Flavors are basically different editions of the application, that are built from a single code base. The library approach is no longer necessary, and the whole process is just streamlined. Through this post, we’re going to configure a build of an application with two flavors, take a look over the process quickly and see how to differentiate the two flavors with different graphics or features.

Note: Android Studio & Gradle Android plugin are still in preview mode and different versions aren’t necessarily compatible, so please take note that the following instructions apply to Gradle plugin 0.9.2 and Android Studio 5.5. If you use different versions than these, they may, or may not work.

Our base application

We’re going to use Android Studio for this app, it would still be relatively easy to apply this without Android Studio though. First, let’s create a new project MultiFlavorSampleApp, with the default empty Activity.

This will get you started with a default app/build.gradle, it should be similar to this:

apply plugin: 'android'

android {
    compileSdkVersion 19
    buildToolsVersion "19.0.3"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 19
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

dependencies {
    compile 'com.android.support:appcompat-v7:19.+'
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

Adding a Flavor

Now we want to add flavor that builds a different edition of the application. Since we don’t have any flavors, we’ll have to define 2 flavors, one for the default edition, and another for the flavor we want to add. Let’s call the default edition “Vanilla”, and the other flavor “Strawberry”.

We’ll need to add a productFlavors section inside the android section.

productFlavors {
        vanilla {

        }

        strawberry {

        }
    }

After modifying your build.gradle, sync your Android Studio with the new changes.
android-studio-sync-gradle
Now if you open the “Build Variants” view, you can easily switch between build variants from within the IDE. A build variant is basically a combination of flavors and build types ( by default debug and release ). So, with 2 flavors, you get 4 build variants by default.
android-studio-build-variants

Now, first thing you would want to do is provide different package names per flavor, so you can distribute them separately.

Update: Check post comment regarding packageName.

productFlavors {
        vanilla {
            packageName "com.example.multiflavorapp"
        }

        strawberry {
            packageName "com.example.multiflavorapp.strawberry"
        }
    }

From the build variants window you can now just change the variant and you’re good to go. So far though, we’re just building the same app with a different package name, now let’s start the fun.

Providing Alternate Resources

Now we’re going to start customizing the application per flavor, starting with resources. As we’ve created a new project in Android Studio, I’ll assume you now have the default project structure:

app/
|--libs/
|--src/
   |--main/
      |--java/
      |  |--...
      |--res/
      |  |--layout/
      |  |  |--activity_main.xml
      |  |--...
      |--AndroidManifest.xml

In this structure, main is your default source directory (i.e. the “unflavored” source). So, where you would put your flavor customizations? in a flavored directory, that is. Let’s say we want to provide a different layout file for activity_main.xml in strawberry flavor. If you follow the same default structure, you won’t need to modify your Gradle script, you just need to provide an alternative source in the strawberry source directory, so basically you will have this structure:

app/
|--libs/
|--src/
   |--strawberry/
   |  |--res/
   |     |--layout/
   |     |  |--activity_main.xml
   |--main/
      |--java/
      |  |--...
      |--res/
      |  |--layout/
      |  |  |--activity_main.xml
      |  |--...
      |--AndroidManifest.xml

Note: You may face minor inconveniences creating files (code files or resources) under a flavor that’s not currently selected from Android’s Build Variants view. Selecting the right flavor before working on its source directory would be more convenient, but not necessary

Now if you build strawberry, you’re going to get the strawberry/res/layout/activity_main.xml layout. If you build vanilla, you’ll get the main/res/layout/activity_main.xml because it wasn’t overwritten.

Note: Be ware of the ids you’re using in the layouts. If you add new ids in strawberry‘s activity_main.xml that are not in main‘s activity_main.xml, these ids will not be visible when building vanilla, resulting build failure if you use these ids in code.

You should note that resources aren’t merged on file-level, but actually on resource-level. For string resources for example, if you have these two files:
src/main/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
           <string name="app_name">Vanilla App</string>
           <string name="hello_world">Hello world!</string>
</resources>

src/strawberry/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
           <string name="app_name">Strawberry App</string>
</resources>

when you build the strawberry flavor, you’ll get these resources (you won’t see this inside the IDE, your files will stay the same, but this just represents how the final resources would be.):

<?xml version="1.0" encoding="utf-8"?>
<resources>
           <string name="app_name">Strawberry App</string>
           <string name="hello_world">Hello world!</string>
</resources>

If you build vanilla, you’ll get main‘s strings.xml as is.

Although this works, I’d recommend splitting customizable strings in a different file (e.g. flavor_strings.xml), to separate actual application strings, from strings that should be customized by each flavor. This way it’s more maintainable, and when adding a flavor, you would know exactly what to customize, without touching the application’s general strings and possibly their localized versions.

Note: When providing alternate resources in a flavor, make sure you provide alternates for all qualifiers that exist in main for the same resource.
For example, if you have logo.png in main provided in drawable-hdpi and drawable-xhdpi, and you only provide the hdpi version in strawberry flavor, the logo.png from main will be loaded on xhdpi devices.

Providing Alternate Code files

Code files are treated differently from resources. They don’t override each other, they’re just combined. So if you have com.example.MainActivity in your main source directory, and provide a different implementation of the same class in a different flavor, the build system will complain that you have duplicate class definitions when building that flavor.

However, this is still easily achievable. If you want to provide different implementations for a class in flavors, you have to omit them from the main source.

Let’s say you want to provide a different class called com.example.Flavor. First you would want to make sure you don’t have the class itself in main source. However, it’s likely that you want a default implementation. So, now we’re going to add it to all flavors separately, but not in main, this way, whenever you build a flavor, it will only see one com.example.Flavor class definition.

app/
|--libs/
|--src/
   |--vanilla/
   |  |--java/
   |     |--com/example/
   |        |--Flavor.java
   |--strawberry/
   |  |--java/
   |     |--com/example/
   |        |--Flavor.java
   |--main/
      |--java/
      |  |--...
      |--res/
      |  |--...
      |--AndroidManifest.xml

Note: If you’re going to use both class versions from the main code files, make sure you maintain the same package, same class name, and same publicly used methods. If one version doesn’t have a method of another, and you attempt to use it, it will result a build error.
This works best if you structure the customizable classes correctly with interfaces or abstract classes. You may be interested to look at Dagger or Roboguice if you want the code be more flexible.

Controlling Code Path per Flavor

If your flavors have different features enabled, you would want to control the code execution per flavor as well. Basically you want to say

if(IS_VANILLA) {
doSomething();
} else if(IS_STRAWBERRY) {
doSomethingElse();
}

You have many options to achieve this.

  1. You can depend on BuildConfig.FLAVOR value, to check which flavor are we building. This method however can be hard to manage if you’re going to add more flavors in the future.
  2. Put your flags in an XML resource file. This will force you to use a Context to get these values in runtime though.
  3. Add a BuildConfig boolean flag, something like (BuildConfig.HAS_PAYMENT, BuildConfig.IS_PRO_VERSION), this could be done using buildConfigField:
    productFlavors {
            vanilla {
                buildConfigField "boolean", "HAS_PAYMENT", "true"
            }
    
            strawberry {
                buildConfigField "boolean", "HAS_PAYMENT", "false"
            }
        }
    

    You can’t add it manually of course as BuildConfig is automatically generated. We’d recommend using this method if applicable.

Note: So far, I’ve been using the default project structure. You can always customize the locations of specific folders (for java files, resource directories…etc), but using the default structure will keep your build script short, and following the convention will help you find stuff easier instead of tracing around where files are coming from.

Signing Configuration

We’re almost done for publishing the applications. We need to add the signing configuration to build signed APKs. I will assume you have already generated the keystore files. You will need to add the signingConfigs section, anywhere before the productFlavors section:

signingConfigs {
        release {
                storeFile file("../../app.keystore") //Path to the keystore file
                keyAlias "app"
                storePassword "12345678"
                keyPassword "12345678"
        }
}

You may prefer to sign each flavor with different certificate. To do that, you would just need to write a signingConfig section per flavor and assign it to each flavor:

   signingConfigs {
        vanilla {
                storeFile file("../../app.keystore") //Path to the keystore file
                keyAlias "vanilla"
                storePassword "12345678"
                keyPassword "12345678"
        }

        strawberry {
                storeFile file("../../app.keystore") //Path to the keystore file
                keyAlias "strawberry"
                storePassword "12345678"
                keyPassword "12345678"
        }
    }

    productFlavors {
        vanilla {
            packageName "com.example.multiflavorapp"
        }

        strawberry {
            packageName "com.example.multiflavorapp.strawberry"
        }
    }

    buildTypes {
        release { //Only use the release key on a release buildType
            productFlavors.vanilla.signingConfig signingConfigs.vanilla
            productFlavors.strawberry.signingConfig signingConfigs.strawberry
        }
    }

Note: For simplicity, I’ve put the keystore and alias password in the build file directly. Most probably, you want to move these out of the file so you can check it into version control, but that’s a bit beyond our scope here.

That’s all for this post. There’s a lot more you can do with flavors, and more configuration, like flavor-specific dependencies that were not covered here. You should not overuse flavors though, unless these flavors should really belong to the same code base. If you find yourself doing major work inside a specific flavor, you should probably reconsider using flavors at all. I think it’s better to think of a flavor as a thin layer, not an app in itself
.

References

Android is a trademark of Google Inc. The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.

Hassan Ibraheem

Senior Android Developer. Boilerplate code hater.

45 thoughts on “Building Multiple Editions of an Android App with Gradle

      1. Is it possible to merge two product flavors which may have similar classes.

        For example, if I want to achieve something like vanillaStrawberryDebug with source sets from vanilla as well as strawberry but having same class in both the source sets (ex. ClassA.Java)

        Look forward to your inputs.

        Thanks

  1. OK, so I followed your advice and created flavors “foo” and “bar” in app/build.gradle. I have one TextView in one layout file (app/src/main/…), and want it populated with a customize string, depending on the flavor. So, I created a “foo-strings.xml” in app/src/foo/res/values/ with the entry FOO.

    When I try set the TextView’s android:text=”@string/app_class”, I get “Couldn’t resolve resource @string/app_class”. (The module “app”s Build Variant is set to fooDebug. I must be missing something pretty simple, but I’ve looked over your post several times and I sure can’t see what it is. Help, please? Thanks!

    1. Never mind! I hadn’t defined it in the main resource file.

      Thanks for this great tutorial! I’m now well on my way to porting my three-in-one iOS app to Android!

      1. Glad to know your problem was solved 🙂

        Good luck porting the app. Let me know if you face any problem.

  2. HASSAN IBRAHEEM, I have a question. If I just what have a different source file for debug version, so how can I do this ? If there are other product config exist, when I put a file in debug, it will tell me a duplicate file.

    1. You’ll have to either remove that file from ‘main’ source set to a separate source set (flavor or otherwise), or you can merge the implementations in 1 source file and control execution with a specific flag.

    1. Hi,
      Thanks for pointing it out. I’ve added a note to this comment in the post for now.

      Since packageName was changed to applicationID in a version released after the post, I think I should go over the snippets to make sure they’re up to date when time is available.

  3. There are also build types which also can have their own source sets. Then what is the difference between flavors and build types, or in which case to choose one over another?

    My app can be built against several environments, like dev, demo, prod, the only difference being in the resources I call (urls). Which will be more appropriate, a build type or flavor?

    1. While I think you can use either. It’s easier to define build types as the variants that affect the “build”, as defined by the development team process or something like that. You would use flavors when the variants affect the “product” dimension, as in a ready to publish APK (Free, Pro, HD, or in multi-apk distribution, etc)

      That’s not necessarily a rule, but it can make it easier to approach. The android gradle plugin will combine the flavors and build types into build variants, so each flavor will be built with all build types.

  4. Hey Hassan,

    Thanks for this amazing article. As a newbie this guide will help to build two versions of the same mobile application very easy….
    Thanks again for these valuable source.

  5. Just to confirm, I will not able to install multiple product flavors on same handset simultaneously as it will give an error for package name conflict.

    1. That’s true, but it can be easily configured by using applicationId or applicationIdSuffix inside the flavor section to give each flavor a unique package name.

  6. Just wanted to add that you can also use placeholders in your Manifest file to reference the applicationId for each flavour. This is useful, for example, if you’re using Content Providers, which require a unique Authority. You could use the applicationId in the Authority string so that it’s unique for each app, allowing both apps to be installed at the same time, avoiding the “INSTALL_FAILED_CONFLICTING_PROVIDER” error. Details on placeholder support in Gradle can be found here.

    1. I have a content provider defined in a library which is used by an application that is available in 2 product flavors. I can get the applicationId(which i would be using as authority) in the application files, but i dont know how to pass the same to the library? Any Idea?

  7. Thanks for this great post. Learnt a lot.
    When does it make more sense to do an if (BuildConfig.FLAVOR == “someflavor”) { /* … */ } check to add specific behaviour or having two different classes in each flavor?

  8. Thank you so much for a great article. Its really helpful to the beginners to understand the gradle folder structure and enabled multiple builds. Keep it posting..

  9. Hi, Is there is a way to do the same with out creating 2 strings.xml file. The current approach requires we maintain two strings.xml file. That is something that gets updated a lot. Every time I update strings.xml I need to update the other one as well, or this soon becomes a maintenance nightmare.

  10. Can you please help why Im getting this error “Error:(44, 0) Could not find property ‘shaadi’ on GroupableProductFlavor container.

    buildTypes {
    release {
    productFlavors.shaadi.signingConfig signingConfigs.shaadi

    minifyEnabled true
    shrinkResources true
    lintOptions {
    checkReleaseBuilds false
    }

    proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’
    }
    }

  11. But i think flavors are introduced for one app with functionality difference for example free and paid version or ad free version. For apps that are to be made for different brands, creating a branch seems more easy and clean way to do. What do you think?

    Regards

  12. Hi Hassan,
    Please you help me.
    If I have 3 productFlavors (chocolate, vanilla, strawberry).
    And then, I want chocolate and vanilla use the same Flavor.java.
    But the strawberry use the different Flavor.java.
    Can you describe the structure that I must create?

  13. Great article! Helped a lot! Just wondering if is possible to create a DI that uses a XML resources file to resolve dependencies…

  14. I have two flavor f1 and f2 of a project with two developer D1,D2 and i want that D1 should have access of f1 only and D2 have access of f2 only so how can i achieve
    this type of functionality.

  15. To get success in app developing profession and make app popular app developers should use new strategies and technologies according to people’s needs. I am very glad to get all information about Gradle because I am sure that I can make my future bright by using this in mobile app developing profession.

  16. Hello, Thanks a lot! I use this way. But I meet a question is that, I use productfavor to implement program some APP. But I can’t distinguish’ the debug. I want change the APP Application label. Because in my phone, the debug and release APP show the same label name!

    I am waiting for your answer!

  17. Hi,
    I read your full blog that you mentioned above.It is informative blog.I really need this information.I am waiting to get more information from your site..
    Thanks for sharing this to all….

Leave a Reply

Your email address will not be published. Required fields are marked *