Code once, build twice: product flavors at rescue.

Have you ever wondered how to make a free and a paid version of your app with as little effort as possible? Or let’s say that you just need to release two different versions of the same app with a few changes in theme, graphics or functionalities. Did you used the “product flavors” feature of android to do that?

If the answer is NO, then keep reading because this post is for you!

You don’t have to worry about making libraries and managing a clone of the source base to do this job because you can simple take advantage of the power of the amazing build system that comes free with android studio: Gradle!

If you have already an understanding of build variants, types and flavors, then go straight to the example section below, otherwise follow me because I will dive a little bit into the theory behind those concepts.

Build Variants

Let’s start from the most general concept, build variants.
Gradle allows you to define several build variants. A definition of build variant consists of all properties of a specific build type added to all properties of a specific product flavor.

Whaaat? Build variants, build types, product flavors? This is getting confusing, dude!

Wait wait! Don’t be scared by all those new terms. It’s more easy than it looks at first. Just let’s examine what are built types and product flavors first, so that you can later understand the build variants.

Build Types

You are already using different build types in your project. Check your app/build.gradle file. By default android studio automatically set-up two build types when you create a new project: one named debug and one release. Isn’t this triggering a ring yet?

build variants

The debug version is the one uploaded on your device when you click the run button.

The release version instead, is the one which you will publish and sign with a specific key.

So you were already using the debug build type, but maybe you didn’t know the official name for it 🙂

Product Favours

For each build type (debug, release), it is possible to specify different variations, also named product flavors. For example if you want a free version of your app and a paid version, those two (free, paid) represent the product flavors of your app.

So going back to the build variants, defined like all combinations of all build types (debug, release) and all product flavors (free, paid, just for the sake of this example), we will end-up having 4 build variants:

  1. freeDebug
  2. paidDebug
  3. freeRelease
  4. paidRelease

That was the theory. See? It was not that difficult! 🙂

Examples

Now let’s see some code on how to implement the product flavors.

First of all you need to add a so-called productFlavors container to the android configuration in your build.gradle file (the one in the app module).

app gradle and product flavors

If you “sync the project with gradle files”,

sync project

then you should see the new build variants.

all build variants

At this point you can start adding the properties specific to each variant.

For example, if you want to be able to install both flavors of the app (free, paid) on the same device, or if you want to publish both of them on the play store, then the first thing that you need to do is to differentiate their package name. For this purpose you need to use the applicationId property.

productFlavors {
free {
applicationId "antoniocappiello.com.buildvariantsexample.free"
}
paid {
applicationId "antoniocappiello.com.buildvariantsexample.paid"
}
}

Sync/Rebuild the project again to make the changes effective.
Now you can select the freeDebug and the paidDebug variant in the build variant panel and run them on your device (every time that you change variant, gradle will rebuild the project to apply the properties specific to that variant, so don’t worry if it hangs on for a few seconds)

At this point you will see both variants of the app on your phone, but you will still not be able to differentiate them because there is actually nothing else different beside the package name.

apps

A quick way to distinguish what is what, is to change the launcher icon. Create a new directory under src with the name of the product flavor that we want to differentiate, for example “free”. Inside this new directory create also the directory tree “res/drawable”. And inside drawable place a new icon. For this example I have used the default launcher with the text “free” printed on it.

launcher free

By rebuilding and running again both variants, you can now see two different apps on your device.

launcher 2

At this point you can make your app variants even more different by adding for example some other resources in your free variant, like colours of your theme or some text.

colors

Changing colors

strings

Changing strings resources

By running again both the freeDebug variant and the paidDebug variant you can now clearly see the difference between them.

green paid

Beside those aesthetic changes, you probably want to have different functionalities to be available for each variant. One simple way to achieve this is by defining variables in the product flavor configuration which will be read at run-time. For example with the EXTRA_FEATURE variable as added below, you can have in your app already a different behavior by using a simple if-else.

productFlavors {
free {
applicationId "antoniocappiello.com.buildvariantsexample.free"
buildConfigField 'boolean', 'EXTRA_FEATURES', "false"
}
paid {
applicationId "antoniocappiello.com.buildvariantsexample.paid"
buildConfigField 'boolean', 'EXTRA_FEATURES', "true"
}
}
public class MainActivity extends AppCompatActivity {

private Toast basicToast;
private Toast coolToast;

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

basicToast = Toast.makeText(this, "This is just a free app", Toast.LENGTH_SHORT);
coolToast = Toast.makeText(this, "This is the paid app", Toast.LENGTH_SHORT);

findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(BuildConfig.EXTRA_FEATURES) {
executeCoolMethod();
}
else {
executeBasicMethod();
}
}
});
}

private void executeBasicMethod() {
basicToast.show();
}

private void executeCoolMethod() {
coolToast.show();
}
}


Screen Shot 2015-10-21 at 23.48.34

Naturally you can expose to your code also other kind of variables, like “Strings”, which come very handy when you want for example to use different google analytics tracking id’s.

android {
...
defaultConfig {
...
}

productFlavors {
free {
applicationId "yourpackagename.free"
buildConfigField 'boolean', 'EXTRA_FEATURES', "false"
buildConfigField 'String', 'GOOGLE_ANALYTICS_TRACKING_ID', '"UA-XXXXXXXX-1"';
}
paid {
applicationId "yourpackagename.paid"
buildConfigField 'boolean', 'EXTRA_FEATURES', "true"
buildConfigField 'String', 'GOOGLE_ANALYTICS_TRACKING_ID', '"UA-XXXXXXXX-2"';
}
}

}

However the capabilities of the productFlavors do not stop here. You can combine it with more properties which normally belong to the defualtConfig element which you may have already seen in the gradle file in the examples above. Also this one is set-up automatically when you create a new project.

When you use one of those properties in the productFlavor, they will automatically override the value of the same properties in the defaultConfig element.

You have already seen the applicationId property, it actually belong to the defualtConfig element, but in the example above we have overridden it in our product flavors.
Another very useful property is signingConfig, which you may need when automating the generation of the release type, since you may want to sign with different key each variant in order to be able to publish both of them on the store. But this require a bit of more effort to set it up, so I am going to give more details on who to use it in a separate post.

If you are interested to know more about the available properties for the defaultConfig then you can read the official documentation here:
http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Basic-Build-Customization .

To summarize what you have learned so far, you are now able to:

  • add a productFlavor configuration to your gradle file;
  • override existing build properties, like applicationId
  • add custom properties like strings and booleans;
  • install two different build variants of the same app on one device;
  • automatically change the behavior and the look and feel of your app depending from the variants that is build.

Hope that those tips are useful for you. You are now one step closer to become a professional android developer 🙂

Keep following the next blog posts because I am going to cover other aspects of the build system which will come very handy for your daily work.

Stay tuned!

(All the code is available on GitHub)

Advertisements

3 thoughts on “Code once, build twice: product flavors at rescue.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s