Android Studio 1.1
Mac OSX 10.10.2
Our base applicationWe’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:build.gradle
Adding a FlavorNow 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.
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.
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.Note: The packageName is not supported any more. Instead, using applicationId.
Providing Alternate ResourcesNow 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.xmlIn 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.xmlNote: 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 necessaryNow 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.xmlsrc/main/res/values/strings.xml
Providing Alternate Code filesCode 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 mainsource 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.xmlNote: 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 FlavorIf your flavors have different features enabled, you would want to control the code execution per flavor as well. Basically you want to sayif(IS_VANILLA) {doSomething();} else if(IS_STRAWBERRY) {doSomethingElse();}You have many options to achieve this.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.Put your flags in an XML resource file. This will force you to use a Context to get these values in runtime though.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 ConfigurationWe’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