Gradle Build – Part 7

Previously in this series we’ve looked at various aspects of the Android Gradle build some of which are features of Gradle itself, but others which are specific to the Android build toolchain that has been developed on top of that. In this article we’re going to look at a feature that most definitely falls into the second of these categories: Build Flavors.

Anyone who has developed an Android app which consists of free and paid versions of the same app will understand that it can be tricky to get your project configured correctly. When you have a lot of common code between the free and paid versions, it does not make sense to simply have two copies of the shared code, because it becomes difficult to maintain – each time you make a change to on project you have to make the same change to the other. The normal method of working around this is to have three projects: One produces the free APK, the second produces the paid APK, and the third is an Android library containing all of the shared code. While this can work rather well, it is a whole lot easier in Gradle because of build variants which are part of the Android plugin.

In a nutshell build variants enable you to create custom build artifacts – for example multiple different APKs from a single project. There’s actually a bit more to it than that because a build variant is the combination of a build type and a build flavor.

A build type allows us to control various aspects of how the build artifacts are produced. As standard there are two build types defined for any project: debug and release. For example, a debug build will be signed using our debug key, but a release build will be signed using a different release key.

A build flavor allows us to control various aspects of the functionality which will be included in the build output. An example of this is free and paid versions our app.

A build variant is a combination of the build type and build flavor, and a separate build output will be generated for each permutation of type and flavor, and eaxh variant will result in a distinct

An example here would help to explain things. Let’s consider that we have an app for which we want to produce two version: free and paid. On top of that we wish to produce versions of each for both Google Play and Amazon Appstore. The free and paid versions will be functionally different (the free version will be feature limited, or will contain advertising), and so we model this as two distinct build flavors. The Google Play and Amazon Appstore versions of each of these flavors will be functionally the same, but will need to be signed differently, so we model this as two distinct build variants. We’ll actually configure the existing “Release” type to produce our Google Play variant, and add a new build type for Amazon.

When we perform a build, a number of separate APKs will be produced:

1

2

3

4

5

6

debugFree

debugPaid

releaseFree

releasePaid

amazonFree

amazonPaid

So all of the *Free variants are functionally the same, but are signed differently; all of the *Paid variants are functionally the same, but are signed differently; but the *Free variants are functionally different from their corresponding *Paid variant.

I’ve made a point of stressing the difference between changes in functionality, and changes to the metadata of the build, and this is fundamental to understanding the differences between build types and build flavors. The reason for this is that during development you may need to debug the two functionally different variants independently of each other, so require multiple debug builds in order to do this. However, you do not need to independently debug the Google play and Amazon Appstore variants if they are functionally identical.

So let’s look at how we can define our own flavors. In our test project we have two projects: a library project, and our main APK project. Suppose that we want to produce two separate APKs, one which is includes the library project, and another which does not. Also, as we’re going to publish both of these variants, we need to have separate packages specified in the Manifest of each APK so that they may uploaded as distinct apps to Google Play. We can achieve this by modifying the build.gradle in the GradleTest project (our APK project):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

apply plugin:'android'

dependencies{

compile'com.android.support:support-v4:18.0.0'

}

android{

compileSdkVersion rootProject.compileSdkVersion

buildToolsVersion rootProject.buildToolsVersion

defaultConfig{

minSdkVersion rootProject.minSdkVersion

targetSdkVersion rootProject.targetSdkVersion

}

productFlavors{

simple{

packageName"com.stylingandroid.gradle.simple"

}

complex{

packageName"com.stylingandroid.gradle.complex"

dependencies{

complexCompile project(':GradleLibrary')

}

}

}

}

Here we are defining two flavors named simple & complex. We have removed the common dependency on GradleLibrary and moved this into the build configuration for complex. We also define the packageName for each of our flavors.

One important thing to note here is that the dependency specification changes from compile project(':GradleLibrary') to complexComile project(':GradleLibrary') – it must be prefixed with the name of the flavor. If you fail to do this, the dependency will be applied to all flavors even though it is only defined within the build configuration for the complex flavor.

If we perform a build, we can see some additional tasks being performed during the build:

Shell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

$ /gradlew --daemon clean assemble

:GradleLibrary:clean

:GradleTest:clean

:GradleLibrary:mergeDebugProguardFiles UP-TO-DATE

:GradleLibrary:packageDebugAidl UP-TO-DATE

:GradleLibrary:prepareDebugDependencies

:GradleLibrary:compileDebugAidl

:GradleLibrary:generateDebugBuildConfig

:GradleLibrary:mergeDebugAssets

:GradleLibrary:compileDebugRenderscript

:GradleLibrary:mergeDebugResources

:GradleLibrary:processDebugManifest

:GradleLibrary:processDebugResources

:GradleLibrary:compileDebug

:GradleLibrary:processDebugJavaRes UP-TO-DATE

:GradleLibrary:packageDebugJar

:GradleLibrary:packageDebugLocalJar UP-TO-DATE

:GradleLibrary:packageDebugRenderscript UP-TO-DATE

:GradleLibrary:bundleDebug

:GradleLibrary:assembleDebug

:GradleLibrary:mergeReleaseProguardFiles UP-TO-DATE

:GradleLibrary:packageReleaseAidl UP-TO-DATE

:GradleLibrary:prepareReleaseDependencies

:GradleLibrary:compileReleaseAidl

:GradleLibrary:generateReleaseBuildConfig

:GradleLibrary:mergeReleaseAssets

:GradleLibrary:compileReleaseRenderscript

:GradleLibrary:mergeReleaseResources

:GradleLibrary:processReleaseManifest

:GradleLibrary:processReleaseResources

:GradleLibrary:compileRelease

:GradleLibrary:processReleaseJavaRes UP-TO-DATE

:GradleLibrary:packageReleaseJar

:GradleLibrary:packageReleaseLocalJar UP-TO-DATE

:GradleLibrary:packageReleaseRenderscript UP-TO-DATE

:GradleLibrary:bundleRelease

:GradleLibrary:assembleRelease

:GradleLibrary:assemble

:GradleTest:prepareGradleTestProjectGradleLibraryUnspecifiedLibrary

:GradleTest:prepareComplexDebugDependencies

:GradleTest:compileComplexDebugAidl

:GradleTest:generateComplexDebugBuildConfig

:GradleTest:mergeComplexDebugAssets

:GradleTest:compileComplexDebugRenderscript

:GradleTest:mergeComplexDebugResources

:GradleTest:processComplexDebugManifest

:GradleTest:processComplexDebugResources

:GradleTest:compileComplexDebug

:GradleTest:dexComplexDebug

:GradleTest:processComplexDebugJavaRes UP-TO-DATE

:GradleTest:validateDebugSigning

:GradleTest:packageComplexDebug

:GradleTest:assembleComplexDebug

:GradleTest:prepareComplexReleaseDependencies

:GradleTest:compileComplexReleaseAidl

:GradleTest:generateComplexReleaseBuildConfig

:GradleTest:mergeComplexReleaseAssets

:GradleTest:compileComplexReleaseRenderscript

:GradleTest:mergeComplexReleaseResources

:GradleTest:processComplexReleaseManifest

:GradleTest:processComplexReleaseResources

:GradleTest:compileComplexRelease

:GradleTest:dexComplexRelease

:GradleTest:processComplexReleaseJavaRes UP-TO-DATE

:GradleTest:packageComplexRelease

:GradleTest:assembleComplexRelease

:GradleTest:assembleComplex

:GradleTest:prepareSimpleDebugDependencies

:GradleTest:compileSimpleDebugAidl

:GradleTest:generateSimpleDebugBuildConfig

:GradleTest:mergeSimpleDebugAssets

:GradleTest:compileSimpleDebugRenderscript

:GradleTest:mergeSimpleDebugResources

:GradleTest:processSimpleDebugManifest

:GradleTest:processSimpleDebugResources

:GradleTest:compileSimpleDebug

:GradleTest:dexSimpleDebug

:GradleTest:processSimpleDebugJavaRes UP-TO-DATE

:GradleTest:packageSimpleDebug

:GradleTest:assembleSimpleDebug

:GradleTest:assembleDebug

:GradleTest:prepareSimpleReleaseDependencies

:GradleTest:compileSimpleReleaseAidl

:GradleTest:generateSimpleReleaseBuildConfig

:GradleTest:mergeSimpleReleaseAssets

:GradleTest:compileSimpleReleaseRenderscript

:GradleTest:mergeSimpleReleaseResources

:GradleTest:processSimpleReleaseManifest

:GradleTest:processSimpleReleaseResources

:GradleTest:compileSimpleRelease

:GradleTest:dexSimpleRelease

:GradleTest:processSimpleReleaseJavaRes UP-TO-DATE

:GradleTest:packageSimpleRelease

:GradleTest:assembleSimpleRelease

:GradleTest:assembleRelease

:GradleTest:assembleSimple

:GradleTest:assemble

BUILD SUCCESSFUL

Total time: 18.527 secs

$

Nothing much has changed for GradleLibrary, but there are a lot of additional tasks performed because of the flavors that we have added.

If we analyse the contents of the build directory within the GradleTest project we can actually see how the build differs. In build/manifests/complex/release/AndroidManifest.xml we can see the package defined as com.stylingandroid.gradle.complex, but in build/manifests/simple/release/AndroidManifest.xml we can see the package defined as com.stylingandroid.gradle.simple. Also, if we examine the contents of the build/classes directory we can see that the classes from the GradleLibrary project are included in the complex build, but not in the simple build.

Another way that we could utilise build variants is to have separate code for tablet and phone APKs along with some common code, and customise the sourceSet for each flavor.

Build variants are an extremely powerful tool and, with some careful organisation of our projects, we can use some minimal configuration to generate multiple APKs for us.

In the next article in this series we’ll have a look at how we can customise the build even further.

1 Comment

Question: If I place res/drawable-mdpi/a.png under a build variant (say src/complex) and src/main/res/drawable-mdpi has [a.png, b.png] – does a smart merge happen?
I mean, will the final apk contain both a.png and b.png with an overloaded a.png?