Build / Gradle

Gradle Build – Part 4

Previously in this series we’ve looked at getting a simple Gradle build working and also seen how we can tinker with the source trees to do some quite powerful stuff. In this article we’ll look at adding a second module, or sub-project to our existing project and see how much easier is is to manage Android Library projects.

gradle_logoAndroid Library projects have been in existence since May 2010 with SDK tools Revision 6, and are easy enough to use when you have both the library and main APK projects within the same Eclipse project, but are a bit fiddly when it comes to importing third-party library projects such as ActionBarSherlock because they require you to import the full source of that project and build your apklib artifact locally. The reason for this is that pure Java projects can be packaged in to a JAR file which contains compiled bytecode and any resources / config required to execute. Android Library projects consist of Android resource along with Java source and, while it is easy enough to compile the Java bytecode in to a JAR, there is no easy way of including the resources. The Maven build made a good attempt at this with the apklib format, but it is still a little cumbersome. Fortunately the Gradle build now supports a new format named Android ARchive (AAR). This is similar to a JAR, but it allows the inclusion of resources as well as compiled bytecode, and therefore allows an AAR file to be included in the same way as we can include a JAR.

To see how this works, we need to create a new module in our project by selecting “File|New Module…” and then specifying “Android Library”, with a name of “GradleLibrary” and a package name of “com.stylingandroid.gradle.library”. We’ll use the same SDK defaults as before, and create an Activity named LibraryActivity using a layout named main_layout.

Once this has completed, there should be a new folder within our top level project named GradleLibrary/, and “settings.gradle” in the top level project has been changed to include this:

include ':GradleTest', ':GradleLibrary'

The order of these is unimportant because during the build Gradle will determine the dependencies, and build the components in the correct sequence to satisfy these dependencies.

Now if we look in the GradleLibrary/ folder we’ll see a very similar structure to GradleTest/, but there’s a small difference in “build.gradle”:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.5.+'
    }
}
apply plugin: 'android-library'

dependencies {
    compile files('libs/android-support-v4.jar')
}

android {
    compileSdkVersion 17
    buildToolsVersion "17.0.0"

    defaultConfig {
        minSdkVersion 7
        targetSdkVersion 16
    }
}

The only difference between this and the config file for GradleTest/ is that we specify the android-library plugin rather than android one. This performs a similar function to the android plugin except it packages things up in to an AAR instead of an APK. Back in part 2 of this series we discussed how there were a number of separate plugins within the Android Gradle tools plugin, and this is one which allows us to build Android library projects.

All that’s left is to add a dependency to build.gradle in GradleTest (our APK project) so that it depends on GradleLibrary:

.
.
.
dependencies {
    compile files('libs/android-support-v4.jar')
    compile project(':GradleLibrary')
}
.
.
.

However, if we try and build this, we’ll see an error:

GradleTestProject >./gradlew clean assemble --daemon
The TaskContainer.add() method has been deprecated and is scheduled to be removed in Gradle 2.0. Please use the create() method instead.
: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
: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
:GradleLibrary:packageReleaseRenderscript UP-TO-DATE
:GradleLibrary:bundleRelease
:GradleLibrary:assembleRelease
:GradleLibrary:assemble
:GradleTest:prepareGradleTestProjectGradleLibraryUnspecifiedLibrary
:GradleTest:prepareDebugDependencies
:GradleTest:compileDebugAidl
:GradleTest:generateDebugBuildConfig
:GradleTest:mergeDebugAssets
:GradleTest:compileDebugRenderscript
:GradleTest:mergeDebugResources
:GradleTest:processDebugManifest
:GradleTest:processDebugResources
:GradleTest:compileDebug
:GradleTest:dexDebug

UNEXPECTED TOP-LEVEL EXCEPTION:
java.lang.IllegalArgumentException: already added: Landroid/support/v4/app/ActivityCompatHoneycomb;
	at com.android.dx.dex.file.ClassDefsSection.add(ClassDefsSection.java:123)
	at com.android.dx.dex.file.DexFile.add(DexFile.java:163)
	at com.android.dx.command.dexer.Main.processClass(Main.java:490)
	at com.android.dx.command.dexer.Main.processFileBytes(Main.java:459)
	at com.android.dx.command.dexer.Main.access$400(Main.java:67)
	at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:398)
	at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:245)
	at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:131)
	at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:109)
	at com.android.dx.command.dexer.Main.processOne(Main.java:422)
	at com.android.dx.command.dexer.Main.processAllFiles(Main.java:333)
	at com.android.dx.command.dexer.Main.run(Main.java:209)
	at com.android.dx.command.dexer.Main.main(Main.java:174)
	at com.android.dx.command.Main.main(Main.java:91)
1 error; aborting
:GradleTest:dexDebug FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':GradleTest:dexDebug'.
> Running /Applications/Android Studio.app/sdk/build-tools/android-4.2.2/dx failed. See output

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 6.46 secs
GradleTestProject >

The reason for this is that we’re including the Android Support library in both projects and so when building the final APK it is trying to add it twice, hence this error. There are a couple of ways that we can resolve this. The easy way is to assume that if our Library project has a dependency on the Support library, then the APK project will inherit this dependency, so we don’t need to explicitly declare it in the APK project and we can therefore remove it. This won’t work in all scenarios though: If we had two library projects, each of which has a dependency on the Support library, this would not work as each one would include the support library. In such cases we’d need something like the “provided” dependency scope that Maven offers, but this is not directly supported by Gradle. Although there is a request on the Gradle support site to add it which includes some methods of implementing this kind of behaviour.

But when we think about it explicitly including the jar file in each project is fairly inefficient compared to Maven’s dependency management and, as was mentioned in the first article, Gradle supports Maven dependency management. However, for this to work, the libraries in question need to be deployed to a public Maven repository, and the Support library is not available in a public repository. While this may change in the future, Google have actually provided an easy solution to this. In the Android SDK manager we can now install a couple of repository packages named “Android Support Repository” and “Google Repository”. These contain the various Support libraries and Google Play Services library respectively, and they provide access to these as if they were installed in your local Maven repository, and so we can access them from our projects.

So to change the Support library, we must change the dependency declaration:

dependencies {
    compile 'com.android.support:support-v4:13.0.0'
}

The really interesting thing here is if we add the same dependency back to the main APK project as well, the project now builds. The reason for this is this if we explicitly include libraries, then Gradle will always include them and this causes them to be added multiple times if we explicitly include them multiple times. But if we define implicit dependencies, Gradle will detect multiple instances of the same dependency, and manage things for us. This gives a very strong argument in favour of using Gradle’s dependency management.

Another interesting thing is that to use the v7 Support library to get GridLayout support back to API level 7, we have to import a Support Library project in to our Eclipse workspace in order to get the necessary resources. However, the v7 Support library in the Android Support Repository is packaged as AAR, so we can now simply add a dependency to this and we get all of the resources imported with the AAR without having to add a new project to our IDE. This makes life very much easier!

In the next article we’ll have a look at improving and simplifying our build configuration.

The source code for this article is available here.

© 2013 – 2014, Mark Allison. All rights reserved.

Copyright © 2013 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

4 Comments

  1. It would be nice if the examples didn’t use a local jar for the support libraries, but used instead the artifact from the local maven repo. This is much much better practice, particularly when dealing with libraries.

    Local jar gets packaged inside the aar of a library and this can lead to many problems down the line.

    thanks for a great article series!

  2. Isn’t that those thing should be easier than this? I mean if I read this, I can totally add libraries to my project. But in real life, I open Android Studio and I do not see a way to add Android libraries. It is not obvious. And then, I google it and then I see lots of different comments and a couple of ways. What do you think?

    1. From 0.2.0 version there is a simpler way to add library module to your project. On previous versions this functionality was broken.

      You should go to File -> New Module and then select “Library Module”. Once you configured your new lib, you just add

      compile project(‘:YourGreatLib’)

      in build.gradle of your main app. That’s all and it works!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.