Android flavors

August 11, 2017

Let’s say you want to build multiple versions of your app depending on whether it is offered for free or paid. In most cases, each version will need to include custom functionality, i.e., code and graphical experience, but the majority of the code base can be shared. The way to do this is by using flavors.

Android Studio, relying on Gradle, supports flavors by default, feature that was introduced in the Google I/O 2013 conference. Here, you can see the video of the new Android Studio build system that was announced back then.

Flavors go together with the concept of “build type”. A build type refers to settings in order to build and package the app, like apk signing and debugging options. By default Android Studio creates two build types: debug and release. Note that by default no flavors are created. The combination of flavors and build types constitutes what is known as build variant. There is a different build variant for each flavor and build type combination.

When creating a new project in Android Studio, the build variants look like this:

Default android build variants

In our case, we will be creating two new flavors, i.e., free and paid, so the resulting build variants will look like this:

Resulting build variants

How to add new flavors to an Android project

The easiest way to create flavors is by using the “Edit flavors” option in the “Build” menu.

Edit flavors menu

This will open the following screen, which shows the default configuration. You can add your new flavors by clicking on the + sign on the bottom.

As you can see, flavors can have different values for the parameters shown in this screen. In this tutorial we will only customise the Application Id, which is responsible for identifying our app in Android. As you know, you can’t install two different apks in your phone sharing the same application id.

Flavors screen

So, let’s create two new flavors, one for the free and other for the paid version of our app, using the following application ids, respectively:

  • Free flavor: com.a6020peaks.flavors.free
  • Paid flavor: com.a6020peaks.flavors.paid

New flavor configuration

After clicking OK, Android Studio will add the following configuration to our build.gradle file:

productFlavors {
    free {
        applicationId 'com.a6020peaks.flavors.free'
    }
    paid {
        applicationId 'com.a6020peaks.flavor.paid'
    }
}

How does Android Studio handle a multiflavor code base

Previously, we described the steps in order to add a new flavor to your app. Now let’s review how flavors work regarding the code base.

Important to note is that Android Studio doesn’t create any folder structure after adding a new flavor. The reason is that having a flavor doesn’t necessarily mean that you will be adding extra code to the solution. In the words of Xavier Ducrohet:

Creating flavors doesn’t mean you’re going to use custom code for them so we don’t create the folders. You do need to create them yourself.

Here, you can read his full comment on StackOverflow.

All the code and resources that you put in the main folder will be visible by the new flavors that you create in your project. Still, there are a few considerations in order to understand how Android Studio treats files existing in your flavor’s folder. Let’s examine them one by one:

  • manifest.xml: if you add a custom manifest.xml file in your flavour, Android Studio will merge its content with the manifest file existing in the main folder. Here, you can find further details about how manifest files are merged in Android.
  • resources (res folder): resources like layouts and drawables existing in your flavor, will replace the resources with the same name existing in the main folder. The values folder is an exception to this rule. In this case, each specific file will be merged with the respective content in main. The way the merge is done is by giving priority to the values defined in the flavour’s folder. For example, let’s consider the strings.xml file and a configuration like the following: <pre class="brush: xml; title: ; notranslate" title=""><!– Main –>; <string name=”app_name”>Flavors</string> <string name=“welcome_message”>Hello, Flavors!</string>

</pre>

<pre class="brush: xml; title: ; notranslate" title="">&lt;string name="app_name"&gt;Flavors PRO&lt;/string&gt; &lt;string name="web_link"&gt;Visit &lt;a href="www.6020peaks.com"&gt;www.6020peaks.com&lt;/a&gt;&lt;/string&gt;

</pre>

When building our paid version, the resulting strings.xml file will look like this:

<pre class="brush: xml; title: ; notranslate" title="">&lt;string name="app_name"&gt;Flavors PRO&lt;/string&gt; &lt;string name="web_link"&gt;Visit &lt;a href="www.6020peaks.com"&gt;www.6020peaks.com&lt;/a&gt;&lt;/string&gt; &lt;string name=“welcome_message”&gt;Hello, Flavors!&lt;/string&gt;

</pre>

  • code folder: the code available in the main folder will be available in each flavor. Just note that it is not possible to have duplicated classes in the main and flavor’s folders. This can become a maintainability issue for your project. Let’s explore a potential solution to this problem in the following section.

How to reuse code in different flavors

Imagine we have a MainActivity class in our main folder. This class shows a banner with a Google Ad or a custom banner, depending if we have the free or the paid version of our app.

Since it is not possible to have duplicate clases in the main and the flavour’s folder, an easy solution would be to delete the MainActivity class from main and create a different version for each flavour. However, this has the inconvenient of duplicating code. This can be even worse when you start adding extra flavours. Imagine you have 4 different flavors, 3 of them sharing the same functionality and only 1 of them requiring a customisation. In this situation, making 4 copies of MainActivity doesn’t make much sense.

Fortunately, there is another possibility. Android Studio allows us to create extra source folders that can be shared among flavors in a similar way to how the main folder is shared. The difference is that we can configure which flavors should use the new source folder. This is a powerful mechanism, which allows us to reuse the common code and only customise what is required in a specific flavour. Let’s see how we can apply this to our 2 flavors project.

The following screenshot depicts the folder structure we have chosen for our solution:

Folder structure for our multiflavor project

We have defined a MainActivityBase as an abstract class in the main folder. As shown below, this class defines an abstract method called customizeLayout.

public abstract class MainActivityBase extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        customizeLayout();
    }

    protected abstract void customizeLayout();
}

This method will be implemented by MainActivity, for which exists a copy in the common and the free folders. The free flavor will have a custom implementation of this class:

public class MainActivity extends MainActivityBase {

    @Override
    protected void customizeLayout() {
        AdView adView = (AdView) findViewById(R.id.adView);
        AdRequest adRequest = new AdRequest.Builder()
                .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
                .build();
        adView.loadAd(adRequest);
    }
}

The common folder contains the implementation that will be shared. In our case only the paid flavor will make use of it.

public class MainActivity extends MainActivityBase {

    @Override
    protected void customizeLayout() {
        // Empty
    }
}

Note that this method is empty, because all the needed customisation is happening in the XML layouts.

In order to indicate our build system that the paid flavour should use the common implementation, we need to define a sourceSet in our gradle configuration. This can be done as follows:

sourceSets {
    paid.java.srcDir 'src/common/java'
}

Like this, when building the paid version, Android Studio will find the MainActivity implementation in the common folder.

The resulting screens for both free and paid versions can be observed below:

Free flavor overview

Paid flavor overview

You can find the source code for this tutorial in the 6020 peaks Android repository on Github.

Header photo by Nick Torontali.