Part 2: A beginner guide to Android build files

Gaurav
MindOrks
Published in
9 min readAug 14, 2018

Recently I shared an article covering basics of build files in an Android project. Many of readers appreciated and I think there are few more topics I could cover. Most of the articles on the web are about Android project and Gradle itself, very few touch Android multi-modules and how build files play their role.

You should read this article if:

  • You are new to Android
  • You are not sure about so many build files in Android project and rely on Sync Gradle files everytime
  • You want to learn some tips and tricks
  • You read the part 1

In part 1, we touched a few topics. We learned what Gradle does in very simple form. We learned what is build script and structure of content inside the build file. If you have not read Part 1, I recommend reading so.

I would like to showcase a sample Android project:

It is an average size project I worked on. It consists of 5 modules (folder with bar chart symbol) which we wrote as shareable independent libraries for internal use. One suggestion: A module helps you thinking in API terms and maintains the clear boundary of who can use what. If you have not created a module to date, do it right after reading this article.

Let’s see one by one what these extra files are.

gradlew and gradlew.bat: These files are to save you some time. When you create a Gradle based Android project, it adds one folder named .gradle which contains the Gradle tasks downloaded for any particular version. As your project grows, you add new dependencies which may need a different version of Gradle and thus your project can have many gradle versions downloaded over time. For example:

Now which version of gradle to execute when you run any Gradle command? Gradle recommends solving this by Gradle wrapper.

Wrapper helps you by executing the right tasks, instead of saying every time you need any particular version to execute, wrapper makes it transparent by executing the right version for you. In addition, if you do not have a local copy of that gradle version, it will download for you. Think your colleague updated the gradle, this wrapper will save you time by downloading the right files for you. It is not a good practice to save .gradle folder in your version control. Just use the wrapper and let it do all the magic.

gradlew is simply a shell script file which lets you interact with the wrapper. e.g on Mac:

./gradlew — version

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Gradle 4.4
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Usually, you should aim to use latest gradle version but as you update your Android plugin and other dependencies with time, they will force you to update gradle as well. Like your software, gradle is also a software and it aims to become better with each release. For example, it can improve the caching of your artifacts. It can fasten the sync time. It can add more input blocks and make your build more configurable. It can add more programming language support. I can support defining constants so you do not need to define versions of your dependencies everywhere and so on. Essence is it improves over time. It is like any Man-made creation, they improve over time except the Man himself.

Android Project and Module type

A module is simply a separate piece of the codebase which we create for so many reasons. We might want a reusability. We might want proper API and contract in place. We might want to share responsibility with different teams and so on. Another important reason is we might want a module because of build structure. Yes, you read right. You might want the output as a JAR, as AAR, as apk and so on. I hope you remember from part 1 that gradle is just a tool to execute a few tasks(scripts). The outcome of a dish depends on the recipe you use, the same vegetable/meat can be converted into various forms. Likewise, the outcome of a codebase depends on the tool you use to convert your codebase. In Gradle, that usually means one line.

apply plugin:

The plugin is simple the recipe, the process needs to be applied to the underlying codebase. Few examples of plugins are:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'io.fabric'
apply plugin: 'com.android.library'apply plugin: 'com.android.feature'apply plugin: 'java-library'...

The final outcome of your module depends on which plugin your module applies. Tip: You can see the outcome in build -> outputs folder in your module. Simply click on Make project and it will run all Gradle tasks offered by those plugins.

Tips and Tricks

Multiple Android apps in one Project

Did you know you can have many app modules in one Android project? Android or Gradle project is just a few folders stacked together which some codebase and applied gradle plugins. We apply these gradle plugins through build.gradle file and the output of a module is dependent on which gradle tasks were executed. In reality, Android app is a module with

apply plugin: 'com.android.application'

and app is just a name for your reference, You can have any name you desire.

Android studio offers you a utility to run the app in your phone. You must have used this:

All it does is, run the gradle tasks for app module and gets the result which is apk because of plugin applied to app module and then uses adb command to install the apk. You can do all this manually if you want OR if you have too much time to kill.

Android studio is smart, if you have multiple modules with plugin: ‘com.android.application’ in your project, Android studio will offer all these options in your run dropdown.

For example, I have two apk modules

Now you should have more clarity on what a project is. A project is not an app. You can have multiple apps in one project. You should have OR not, I leave that to you.

Manifest PlaceHolders

As we know now the build files are just there to provide input to gradle tasks. A simple and powerful utility is ‘manifestPlaceholders’. You can define constants in your build file and use them in AndroidManifest.xml.

How to do?

If you want some constant throughout all of your buildtype (debug, release etc), then you can define in defaultConfig block:

defaultConfig {
manifestPlaceholders [key1: 'value',key2 : 'value']
...

If you want different value in different build type then you can define these placeholders under build type block.

buildTypes {
release {
manifestPlaceholders [key1: 'value',key2 : 'value']
}
debug {
manifestPlaceholders [key1: 'diffvalue',key2 : 'diffvalue']
}

You can use these keys as your manifest file with these syntax.

${key1}

When you assemble your build, some gradle task has been given the responsibility of replacing these ${key1} by values present in build files.

Extra Properties

Almost every Android project uses dependencies. See, Engineers are also social sometimes. We already know we have to define those dependencies as:

Vendor:libraryName:Version, example:

'com.android.support:appcompat-v7:27.1.1'
'com.android.support:recyclerview-v7:27.1.1'
'com.android.support:design:27.1.1'
'com.android.support:cardview-v7:27.1.1'
'com.android.support:customtabs:27.1.1'

There are two issues here:

  • The versions are repeated (Repetition: maintenance flaw spotted)
  • They look ugly

There is a better way, every engineer said at-least once

Gradle offers extra properties block. We can define properties like:

ext{
//dependencies
myVersion = '4.12'
yourVersion = '1.10.19'
supportVersion = '27.1.1'
}

Now, in our build files we can simply use these keys.

dependencies {
...
compile "com.android.support:appcompat-v7:$rootProject.ext.supportVersion"
compile "com.android.support:support-v4:$rootProject.ext.supportVersion"
...
}

You can also define ext in your app module build.gradle file directly:ext.supportLibraryVersion = '23.1.1'
dependencies {
...
}

Dependency Graph

This is one of best command gradle offers, If you run

./gradlew app:dependencies , you get following output

+ — — com.android.support:appcompat-v7:27.1.1
| | + — — com.android.support:support-annotations:27.1.1
| | + — — com.android.support:support-core-utils:27.1.1
| | | + — — com.android.support:support-annotations:27.1.1
| | | \ — — com.android.support:support-compat:27.1.1
| | | + — — com.android.support:support-annotations:27.1.1
| | | \ — — android.arch.lifecycle:runtime:1.1.0
| | | + — — android.arch.lifecycle:common:1.1.0
| | | \ — — android.arch.core:common:1.1.0
| | + — — com.android.support:support-fragment:27.1.1
| | | + — — com.android.support:support-compat:27.1.1 (*)
| | | + — — com.android.support:support-core-ui:27.1.1
| | | | + — — com.android.support:support-annotations:27.1.1
| | | | + — — com.android.support:support-compat:27.1.1 (*)
| | | | \ — — com.android.support:support-core-utils:27.1.1 (*)
| | | + — — com.android.support:support-core-utils:27.1.1 (*)
| | | + — — com.android.support:support-annotations:27.1.1
| | | + — — android.arch.lifecycle:livedata-core:1.1.0
| | | | + — — android.arch.lifecycle:common:1.1.0
...
...
...

Basically, it lists down all dependencies you have and your dependencies have. The whole graph at your disposal.

Sometimes it helps which dependencies are being for debugging and solving issues like a version mismatch.

Version Mismatch

There is a possibility that you are using some dependency directly and indirectly. Direct dependencies are which you defined in build.gradle file and indirect are dependencies added to your project because it is being used by some other dependency you need.

So there is also a possibility the version of some dependency may mismatch. Either you did not update the version with time OR they did not, the end result is crying gradle and frustrated you. There are a few ways you can solve this issue.

  • You can exclude a particular version, suppose you want to exclude a version of abc library in xyz dependency

implementation xyz {
exclude group:’vendor-abc’ module:’abc’
}

Now, whatever version of abc is defined in xyz dependency, it will be ignored and xyz will be forced to use the other version you have. Note, if you do not have abc then xyz will not compile and gradle will fail the build.

Summary

We can conclude part2 by saying gradle is just there to make your life easy and it offers many utilities, gradle wrapper is one example. An Android project does not mean one app, it depends on which type of module it is. We define module type by plugin name. A plugin is just a collection of tasks which by executing will offer different output from the same codebase. Jar, AAR, apk are few outcomes. You can write Gradle script in Groovy (Kotlin as well), which is JVM based. Manifest placeholders make your configuration simple and dynamic. A good use case of extra properties placeholders are there to manage dependencies better and finally, the dependency graph is there to help you in debugging and resolving version mismatch conflicts.

I hope you learned something new.

Read Part 3: Beginner Guide to CI/CD here

Writing takes time, Usually 1 day to write an article which offers months of learning. 👏 clap 👏 & share if you like it and follow me(Twitter) for the updates. Share, Comment or start a discussion. Feel free to correct mistakes.

Credits: Originally published here on my blog.

--

--

Gaurav
MindOrks

Tech Enthusiast, Trainer & Wannapreneur, gaurav-khanna.in ( Want to happy, healthy and productive? GoodApp https://share.goodapp.in/gaurav)