Development is not easy and build system can be more confusing. I feel your pain.

The android project structure is not easy to adapt at first glance, especially if you have a multi-module project. This article aims to simplify an important aspect of Android project structure: build files. Is this article for you? Will it add value? Depends on:

  • If you are a new Android developer, buckle your belt. It surely will help
  • If you are one of those developers who click sync now whenever Android studio asks you; you definitely need this article

  • If you are experienced developer and comfortable with Android build files and know in and out, read for proofreading and revising. I will appreciate leaving a note on how to improve this article
  • You do not want to waste time on Social media and do not know what else to do

Let’s start.

Section 1 Gradle

First thing first, Android Studio supports Gradle as its build automation system. Gradle is a nice tool and a detailed explanation is out of the scope of this article. You need to read books for that. Believe me, reading is good. For beginners, I will explain in a very basic way what it is. Experienced can skip this section.

If you are a software developer, you write code. You do not send your code to users. Any software you write in any platform, you need to perform some steps before you can release OR to convert in the form you can share with your users. In Java, it is a jar, Windows exe, Mac dmg, Android apk and so on. To convert your code into the required output, you follow certain steps. The steps can vary depending on the platform you work with. For example compilation, code stripping, deleting unused code, optimizing code, can be few steps before the final result.

Now, these steps are finite and sequential. Let’s say you can run each step with a command.

  • Add time logging in each method
  • Compile code
  • Strip unused code
  • ……

Either you can run these commands manually OR ask a computer to do for you. Whenever something is algorithmic in nature, automation is the right way to go. Gradle, my friend is just this, a tool/script to run the commands required to build the final binary (apk in Android).

Note:The code you write does not execute as it is. Indirectly you convert it by adding/stripping/changing/ optimizing before you release binary(final format).

Dependency

Your Android project does not suffice on its own. First of all, it is dependent on the Android classes and mostly Android support classes. 99% your project also depends on 3rd party libraries. It can be for HTTP, image loading, database, some native processing, and so-on. These are called dependencies: Well written utilities can be reused by other projects. The format of these dependencies also depends on the framework you are programming in. It can be an Android aar, Java Jar, Ruby gem and so on.

There are again two ways to use dependency:

First is manual and Second is automation. Manually you can take the dependency output file and drop in your project and then add to classpath so your tools can find them and your classes can use them. Every time a new version is released, you need to keep an eye. Not more than a few years back, developers were supposed to do it. It is just too tedious to do all this. Why waste time on duplicate and boring efforts when we can watch Star Wars 7th time. After all, we are developers.

Second and Better way is to ask the computer to do all this repetitive and boring job for us. We can tell the computer program what all dependencies we need and it will obey our command. It will fetch those output files and place it in the exact position required. No more errors and no more boring work. We can continue our interesting Gossip over coffee.

Summary Section 1

To convert your raw code into something executable, you need a few other libraries (dependencies) and you need to perform certain operations. The operations are also called tasks and they vary depending on the framework you work with. Automating this process of fetching dependencies and running required tasks to generate the executable is called automated build system. Gradle is just that.

Section 2 — Tell Gradle

Now we know from section one Gradle will run build tasks for you and manage dependencies. But how will it do it? Like anything else in Software. Config file. Gradle config files are simply .gradle extension files mainly build.gradle file and settings.gradle file.

In your root directory of the Android project, you will find these two files. An Android project consists of your main module, few third-party dependencies, and your own modules.

Settings.gradle is simply there to say what all modules (subdirectories) gradle needs to take into consideration while building.

This top-level build.gradle file is applicable to the whole project (all modules) and we will cover the file in detail in section 3.

You can have multi-modules in your project. Each module will have its own build.gradle file. Remember each module is itself a complete software which is build to be reused in other projects. So each module can also have dependencies and build tasks to perform.

Similarly the dependencies you use can also have their dependencies and so on. So gradle job is to create a dependency graph and download all required files for you and combine all tasks need to be executed to build one shining binary of your software.

Section 3 — content of top level build files

Now we know from section 2 build.gradle is the file via which we communicate with gradle.

The android project will have many build.gradle files. One per project level and one for each module.

As its name suggests, Project level build file is used to define project-level dependencies and configuration. Whatever you define there is applicable to all modules. That’s why you should be careful and not define any module specific config here. Let’s see a sample build.gradle project level file:

    buildscript {
    repositories {
            jcenter()
            maven { url 
    }
            google()
        }
        dependencies {
            classpath 
    classpath 
    classpath 
    classpath 
    $kotlin_version
    }
    }
    allprojects {
        repositories {
            jcenter()
            maven {
                url 
    }
            google()
        }
    }

Don’t get scared if you do not understand. The first time, Hello world was as scary and now you feel it is natural.

build.gradle file is divided into sections, e.g buildScript and allprojects in above example. Think of it as each section calls a method. Few sections will transform into one Object with the values you specify inside the block. Few method calls will result in calls to other methods.

repository

This is entry you will find at many places in gradle blocks. These are addresses of dependencies. Not all dependencies come from same repo (we humans). So we have to tell gradle where to look for dependencies. Whenever gradle needs something and it can not find, it looks into repositories in the order they are defined and stops as soon it finds.

buildscript

Gradle is a generic system. It executes the tasks. You have to tell which tasks and you have to tell where those tasks are coming from. Suppose you have called some xyz task in your build process, either you write that task OR you use task written by someone. buildscript is the object which contains those initial dependencies for your build tasks. You are using some build tasks and they come from here. For example, in Android, your module can either be an android application plugin (plugin: ‘com.android.application’) OR an android library (plugin: ‘com.android.library’). Both of these plugin types execute different gradle tasks(plugins do more than that, we will see later) and the tasks are defined in android tools gradle file. That’s why we have one entry in buildscript

classpath 'com.android.tools.build:gradle:3.1.3'

If you remove the above line from buildscript, you get the following error:

Simply saying, these plugins enhance gradle build system by offering logic from a centralized place and the classpath is where the plugin is defined.

In essence, it says, these entries are required in my classpath for my build script to run. Hence the name buildscript.

Example of Android plugin process + Gradle flow (Simplified):

Credits: https://developer.android.com/studio/build/

allprojects

This block is self-explanatory from its name. Whatever is defined in this block is applicable to all of the modules. This way you save repetitive lines. In the above example, we are defining repositories.

Section 4 — content of modules build files

The content of build file inside a module depends on what type of module it is. Simplifying more, in Android usually, it is an Android library OR Java library. Based on the type, you will apply some plugin to build file and the tasks in that plugin need some input from you. It may be whether to minify code, optimize code etc. Let’s see an example of one Android module:

    apply 
    : 
    android {
        compileSdkVersion 27
        defaultConfig {
            minSdkVersion 16
            targetSdkVersion 26
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
        androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        testImplementation 'junit:junit:4.12'
     some_3rd_party_lib
        
    some_3rd_party_lib
    }

This is a default file generated by Android studio for you. The first line says It needs android library plugin. That plugin task needs some info from you like what is minSdk you are targeting, do you want to minify, where is your proguard file etc.

We provide that information to the plugin by adding android block

android {
   ...
}

A detailed explanation of what all this block contains and what is meaning of those entries is not in the scope of this article.

And then comes dependencies

dependencies {
...
}

It is an interesting block. Quite recently gradle forced us to define dependencies with implementation, testimplementation, androidtestimplementation and api. What are these?

implementation: This is a normal dependency you need when you build your output. It must be present in all build outputs. Relax, you will understand after reading others.

testimplementation: This is dependency you need when you are running JVM based tests on your machine. Your test might be using mockito or other library and you have to define it here. The benefit of this is when gradle will build your normal output, it knows it does not need to run tasks for this type of dependency and it will not include that output in your final binary. It will save you time and memory both. What else do you need in the computer world?

androidtestimplementation: This is testimplementation + android. Will be used when you are running tests on the device, e.g using expresso framework.

**api : **This is a little tricky and it becomes even more interesting. api is implementation + expose to modules who are dependent on me. Suppose you have one app module and one rating module.

app depends on **rating: **app->rating. ssume rating module is using Lottie library for animations. Now rating module can either define Lottie as implementation OR API. The minor difference will be if it is defined as implementation, classes inside the rating module can access Lottie. App classes have no idea about Lottie. But if you define Lottie as API, then app classes also get access to lottie. Powerful!

Usually dependencies are defined in the format:

Vendor:libraryName:Version, example:
‘com.google.firebase:firebase-core:16.0.1’

Similarly:

implementation fileTree(
: [
], 
: 
)

It says I depend on whatever is inside libs directory and of type jar. It is implementation, it means jar classes are not accessible in the dependent module.

AND

implementation project(
)

this entry says I depend on another module named rating which is in the same root directory as I am.

Summary

Gradle is a generic build automation system which is used to manage dependencies and run tasks required to convert code into the final binary format we need. The tasks are called gradle tasks which are language agnostic. The main two files are top-level build.gradle and setting file. The setting file simply says what are other gradle based modules to consider and top-level build file is where the execution begins. It contains classpath entries on which module gradle tasks are depending. Each module also has build.gradle file to define what tasks need to execute. Gradle re-uses those tasks in the form of plugins. Each module can specify which plugin it wants to apply and pass required info in the form of gradle section object.

Finally, we saw an interesting way gradle allows us to manage dependencies. implementation and API are similar but API has a broader scope. We learned how to depend on the local artifact (jar in our case) and how to depend on other modules (which is a project in gradle vocab).

It simply means soemthing changed in gradle file(CRUD) and gradle needs to sync dependencies. So your code has access to everything latest.

I hope you learned something new. There is a lot more than this introduction. Feel free to share your opinion.

Subscribe to newsletter and keep learning good stuff

Email

👉 If you like article, click on ♥️ recommend and share on social 👥 media.
👉 Feel free to comment 💬
👉 Follow me 👀 for the more interesting articles. Twitter Medium
👉 Subscribe to newsletter and keep learning