Android Gradle configurations

Karol WrótniakAndroid Developer

The basics of Gradle configurations

Almost all Gradle projects (including all Gradle-based Android projects) uses some configurations. What does configuration mean in Gradle? First take a look at the example:

1

2

3

4

5

6

7

8

9

10

dependencies{

annotationProcessor'com.jakewharton:butterknife-compiler:8.4.0'

compile'com.jakewharton:butterknife:8.4.0'

compile project(':api')

debugCompile'com.squareup.leakcanary:leakcanary-android:1.4'

releaseCompile'com.squareup.leakcanary:leakcanary-android-no-op:1.4'

androidTestCompile'com.android.support.test:runner:0.5'

testCompile'org.robolectric:robolectric:3.1.2'

testAnnotationProcessor'org.robolectric:robolectric-processor:3.1.2'

}

Configurations are used here to group dependencies, they are defined using pattern configurationName dependencyNotation. In the example above compile, testCompile and so on are all configuration names and 'com.jakewharton:butterknife:8.4.0', project(':api') are dependency notations. All configurations here are created by Android Gradle plugin.

After project evaluation, each dependency is resolved (with the help of repositories stanza and other rules) to file URI and configuration becomes FileCollection. The latter can be used by various Gradle tasks to achieve their objectives. For example elements of debugCompile configuration are added to debug variant compile classpath.

Configurations in Android projects

Configuration names added by Android Gradle plugin consist of two main parts:

optional prefix denoting build variant, product flavor or build type

required suffix denoting scope

Eg. in debugCompile: debug is a build type (equal to build variant when there are no product flavors) and compile. If there is no prefix configuration is common to all build variants, eg. compile is applicable to both debug and release build types. Each regular configuration has also corresponding unit test one, so by default for compile there is also testCompile, testDebugCompile and testReleaseCompile. Unit test configuration inherits from regular ones (inheritance is described later in this article).

Finally, there is a androidTest configuration group used for instrumentation tests (running on device/emulator). From configurations point of view androidTest is treated as a build type. Note that currently (up to Gradle plugin 2.2.0) only one build type can be instrumentation-tested, so there are no corresponding debug and release configurations. There are also wearApp configurations related to Android Wear, they are not covered by this article.

Scopes

The scope is related to phase when the configuration is used. What phase means here? Let’s take a look a the Figure 1.

Fig. 1. – configurations by phase.

annotationProcessor/kapt/apt

This scope is dedicated to annotation processors like dagger-compiler from Dagger 2. Using this configuration is not strictly required for annotation processing itself to work (provided or even compile in some cases also do the job). However additional scope guarantees that transitive dependencies (like guava or javapoet commonly used by annotation processors) won’t be available for use from application code. If provided is used instead they are available and used by a mistake will cause crash at runtime due to missing classes. In case of compile all the classes from transitive dependencies will be available but method number (which is limited to 65K for dex file) will often increase drastically.

AnnotationProcessor has been introduced in Android Gradle Plugin 2.2.0. Formerly this functionality was provided by android-apt Gradle plugin. In kotlin this scope is called kapt.

provided/compileOnly

In maven and Android Gradle Plugin it is called provided however name compileOnly is self-explanatory this is how kotlin and Java Gradle plugins call this scope. As the second name suggests dependencies declared in this configuration are available only at the compile time but not at runtime. They are not packaged into APK or AAR so attempt to access classes from those dependencies will cause runtime error. There are 2 important limitations of this scope:

dependencies are not transitive so they are not included in dependent projects

dependencies can only be JARs, not AARs meaning they cannot include Android resources, assets, manifests etc.

When should it be used? Typically dependencies containing source-only annotations go here. Some widely used examples of Android applications projects are: Android Support Annotations (however large number of samples use compile scope for it) or lombok (needs to be also in annotationProcessor).

compile

This is the most common scope. Dependencies will be available at both compile and execution time, which is desired in most cases. Eg. butterknife is needed during compilation and also in runtime.

apk

This is rarely used the scope, even not mentioned in the official documentation. Dependencies are available at runtime but not during compilation, this may be useful eg. when using @SneakyThrows annotation from lombok.

Inheritance

Configuration can extend another one which means that it contains also items from parents. Eg. testCompile extends compile so all production dependencies can also be used in unit tests but not vice versa. Robolectric classes can be used in unit test sources but not in production code. Analogously debugCompile extends debug and so on. Particular configuration can inherit from more than one parent. Note that inheritance must be explicitly declared and naming scheme does not imply it eg. testAnnotationProcessor does not extend annotationProcessor.

Custom configurations

Existing configurations can also be extended manually which can be used to achieve useful results. Let’s say that junit and assertj-android are needed in both unit (src/test) and instrumentation (src/androidTest) tests. The most straightforward way to achieve that is to declare each dependency separately in appropriate configuration:

1

2

3

4

testCompile'com.squareup.assertj:assertj-android:1.1.1'

testCompile'junit:junit:4.12'

androidTestCompile'com.squareup.assertj:assertj-android:1.1.1'

androidTestCompile'junit:junit:4.12'

As you can see there are repetitions. To avoid them we can introduce additional configuration, let’s call it commonTestCompile. First we need to create it inside configurations stanza. Along with creation we can configure inheritance telling that appropriate configurations from Android plugin extends from newly created one: