All You Need To Know About Gradle in Android
All You Need To Know About Gradle in Android
Now, we will talk about each one of them and the difference
between them.
What is the difference between
the build.gradle for the app and the build.gradle for
the project?
any module you create has its own build.gradle, and the app is
considered a module, which is why it has its own build.gradle.
On the other hand, the project can contain more than one module,
which is why there is a build.gradle for the app module and
a build.gradle for the entire project.
Any code I write in the build.gradle for the app will apply only to
that app, which is a module.
Meanwhile, any code I write in the build.gradle for the project will
apply to the entire project, including all the modules, one of which
is the app module.
First: build.gradle (Module: app)
o The location of this file is in the module itself: app/build.gradle.
o Its purpose is to specify the configuration for the module, as we
will see.
o If you open the build.gradle file for the app module, you will see
several blocks that we will discuss now:
1- Plugin Block
These plugins are specific to the app module only, meaning I cannot
use them outside the app module in the rest of the project.
2- android Block
The android block contains several attributes and blocks that we will
discuss in detail.
Namespace
com.example.myApplication
The app module is considered the main module for the entire project;
therefore, the namespace is the application ID.
compileSdk
Here, I will specify the Android SDK version that
the app will compile against.
applicationId
o The applicationId is a unique identifier for the app in the project or on Google
Play. There may be multiple apps with the same name in the store, but there
cannot be more than one app with the same applicationId since it is unique.
minSdk
o Setting minSdk to 24 means the app will not run on any device with an API
level lower than 24.
targetSdk
o Setting the targetSdk to 35 indicates that the app is designed to run on devices
with an API level of 35 and it’s preferable for compileSdk to match
the targetSdk.
versionCode
o This is an integer representing the version number, which is important for
updating the app on the app store. It must be incremented each time the app
is uploaded to the store.
versionName
o This is a string that can be in any format, such as "14.", "5", or "41.9." Its
purpose is to display the app's version to the user on the app store.
testInstrumentationRunner
o This is related to testing; it specifies the test runner to be used for running
instrumented tests.
buildFeatures Block
I use the buildFeatures block to enable certain features, such
as viewBinding, dataBinding, or BuildConfig.
buildType Block
Every app has two default build types: release and debug,
which are created with the project.
The release is the production version that goes to the client and
is uploaded to the Play Store for final use.
The debug version is used by developers or testers, allowing
them to track issues or the code during development.
I can easily add another build type by going to
I will notice attributes for the build type, such as the application
ID Suffix in debug. If I specify it as .debug, it will append the
word "debug" to the base package name.
If I want the debug version to have a different app name than the
release version:
- If I click Add Product Flavor, it tells me that I need to have a flavor dimension.
- Therefore, I will choose the first option, Add Flavor Dimension, which will open this
dialog where I will specify the flavor dimension name.
- I will add the first product flavor as free and the second as pro like this:
I will notice that there are attributes for each flavor that I can define,
such as the version code and minSdk, and I can specify these for each flavor.
Now, for example, I will set the ApplicationID Suffix like this:
o Free --> .free o Pro --> .pro
If I go to the build.gradle file for the app, I will find that it has automatically
added this code:
I can add any resource to any flavor, and I can also add it to a specific flavor while
leaving the other flavor without it, in the same way mentioned before:
Here, I will create any resource I want in the same way as before.
I can control the execution of any code in any flavor in the same way as
before, like this:
This code will execute in free and will not execute in pro.
There’s another way to implement different code in each flavor:
Step One:
1. I will display the project in Project View.
2. After that, if I select the build variant, for example, freeDebug, I will create:
src > directory > freeDebug/kotlin
3. A folder named kotlin will be created where I will create a class or file and
define a method, for example, like this, which I will not be able to access
unless I am in freeDebug.
Step Two:
1. I will do the same as before but select a different build variant, for
example, proDebug, and then create : src > directory > proDebug/kotlin
2. A folder named kotlin will be created again, where I will create a class or file
with the same name as before; follow the same System of access, which will
only be available while in proDebug.
Step Three:
If I run the app in free, it will execute the code I created in freeDebug, and if I
run it in pro, it will execute the code in proDebug.
BuildConfig file
buildConfigField(“Boolean”,”isHappy”,”false”)
There are three ways I can add parameters to BuildConfig like this:
If I run the app, I will see a toast with the value from the class,
which is false.
Method Two: buildType
If I add it only to the release, it means I can only use it in the
release, and the same applies to debug.
sourceCompatibility = javaVersion.VERSION_1_8
This line specifies compatibility with the source code.
What does this mean?
It means that the source code will use the features available in Java 8.
This allows me to use features such as lambda expressions, stream
API, and other Java 8 features without facing compatibility issues.
targetCompatibility = javaVersion.VERSION_1_8
This line specifies compatibility with the bytecode.
What does this mean?
It means that the bytecode that is compiled must be compatible
with Java 8. So if there's anything above Java 8, I won't find it in
the bytecode. This way, I can ensure that the generated class file
can run on a Java 8 runtime environment, even if I'm using a newer
version of the SDK for compilation.
When I choose the JVM, it must be suitable for the minSdk, since,
for example, if I choose a low JVM version with a high minSdk, it
won't work.
Before moving on to the next block, let’s talk about
the difference between
I must choose the Java version and Java bytecode version correctly,
because sometimes, for example, I might be using a library that
targets a specific JVM. If the library is compiled, it may produce
bytecode that is not compatible with the JVM I specified.
kotlinOptions Block
jvmTarget = “1.8”
This specifies that the Kotlin code must compile to Java bytecode that is
compatible with Java 8, similar to targetCompatibility.
This ensures that any Kotlin code can utilize the features available in
Java 8 and can also run on a Java 8 JVM.
3- dependencies Block
In the dependencies block, we add the libraries we will need in the code.
There are several keywords we use:
implementation
This is used frequently, and through it, we can add any library or even
another module, and we can use it anywhere in the module, whether in the
package or in tests.
api
This functions similarly to implementation, and we
discussed the difference between them previously.
Module 1
Let’s assume I have three modules, and each module
has a class structured as follows: Class 1
After that, I will implement it the way we discussed
before, which is --> implementation(project(---)) implementation
The question now is: Does module1 recognize class3?
The answer is no, and this is Module 2
what implementation means; it implements the
library only for the module itself, not for the other Class 2
modules that depend on it.
Can I do the opposite? That is, implementation
make module1 recognize class3. Yes, of course, all I
need to do is change the Module 3
word implementation to api in module2 Class 3
like this: --> api(project(":module3"))
This way, I can access class3 in module1.
debugImplementation
This means that these libraries will be used only in debug mode.
releaseImplementation
This means that these libraries will be used only in release mode.
testImplementation
This is specific to the implementation related to local unit tests.
androidTestImplementation
This is specific to the implementation related to tests that require
the emulator to be running during the tests, which refers to
Android tests.
The plugins here are supposed to apply to the entire project, but since
I’ve specified that it’s false, that means it will not apply to either the
project or the modules.
Thirdly: settings.gradle.kts
The settings.gradle is part of the Gradle build system used to configure
and manage multi-module projects.
1. pluginManagement
2. dependencyResolutionManagement
If I run it, I will see that the library has been added, and I can use it easily.
3. name of the root project
This specifies the project name that I defined for the project.
include function
This function is very important because I specify the modules that exist in
my project.
Fourthly: gradle.properties
This is a configuration file used in Gradle builds to define properties
and settings, as we will see shortly.
The gradle.properties file allows me to customize many aspects of the
build process without having to change the build script.
Here, for example, I specify the JVM arguments that Gradle uses for
running. These arguments allocate memory, performance options, and
encoding:
-Xmx8192 --> This option specifies the maximum heap size that the
JVM will use, which is an additional 8 GB.
-XX:+HeapDumpOnOutOfMemoryError --> This is a flag that creates a heap
dump in case an OutOfMemoryError occurs.
-Dfile.encoding=UTF-8 --> This sets the default file encoding for the JVM to
UTF-8.
This enables the Gradle daemon process, which speeds up the build
process.
This allows Gradle to execute multiple tasks in parallel. If you set this to
true, it will execute more than one task at the same time. This certainly
increases performance and improves the build speed by using multiple
CPU cores.
When this feature is true, it allows Gradle to configure only the modules
required for the tasks currently being executed, instead of configuring all
the modules.
This enables AAP2 (Android Asset Packaging Tool 2), which helps
package all the files in the project to generate the APK.
This enables the Gradle build cache, allowing it to use the output
from previous build processes, which increases build speed.
Fifth: local.properties
This is a file among those that exist in my project, and it is a
simple text file generated by Android Studio.
One important thing here is that by default, this file is included
in .gitignore, meaning it is among the files that will be ignored
when I upload the project to any version control system.
2-Sensitive information
If I have sensitive information, such as an api_key, I can
store it in local.properties, as we will see shortly.
3-Convenience
I can clarify or explain the steps for anything I want for
new developers.
Now let's store the api_key in local.properties:
Step One:
I will go to local.properties and write the api_key there like this:
Step Two:
I will go to build.gradle(app) in the default config and add this code:
Step Three:
I will rebuild the project.
Step Four:
Now I can use the api_key in the app easily.
Gradle wrapper file
What is the Gradle Wrapper?
The Gradle wrapper (gradlew) is a small application included in the
source code that downloads and launches Gradle, making build
execution easier.
If you open the Gradle folder, you will find a folder for the
wrapper. This folder contains two files:
First : gradle-wrapper.jar
This is a Java Archive (JAR) file that contains the logic
required to download the Gradle distribution.
Second: gradle-wrapper.properties
This is a file that contains a set of properties which we will
discuss now.
The property specifies the path to the base directory where the
Gradle distribution will be stored:
GRADLE_USER_HOME
This points to the same path, which in Windows is typically:
c:\users\username\.gradle\wrap per\dists
Now, let's understand what happens when I run the
app, in order:
1- Gradle Starts the Build Process
Initially, ./gradlew (the Gradle wrapper) is invoked.
Gradle reads the configuration from the build.gradle file and
determines the tasks to be executed based on the build type
(whether it is debug or release).
2- Dependency Resolution
Gradle checks and resolves the dependencies specified in
the build.gradle file, downloading any necessary libraries or
modules from the repositories defined in settings.gradle.
3- Compilation Tasks
Gradle initiates the compile tasks for the project; then, the
compiler compiles the source code and converts it to bytecode.
To this format:
Forth: For the Build Types, I can't use the name of the buildType
directly; instead, we will use the getByName() method.
From this format (Groovy):
In the catalogs file, we’ll write in the relevant section like this:
Now I can use the library directly via the TOML like this.
Step Four:
If I have a plugin structured like this:
I will do the same as I did with the library and add the plugin under
its corresponding section.
This way, I can use the plugin directly via the TOML.
Step Fifth:
If I want to apply the same logic with the variables found in Gradle, I
will do it like this:
After that, I will use them easily in the build.gradle file like this:
Gradle script
Most of the files we've discussed are Gradle scripts, like for example:
- build.gradle
- settings.gradle
- gradle.properties
This is the root of the project, since I created the Gradle script in
the root; I could also place it in the Gradle file itself, but in this
case, I would apply it like this:
Gradle Tasks
What are Gradle Tasks?
A Gradle task is a small unit of work written in the Gradle build
script. Each task presents a specific action for execution, like
generating an APK from the project, installing the app on the
emulator, or checking the app's code for issues and suggestions
on how to resolve them. There are many other tasks as well, and I
can make a task depend on another task, as we will see shortly.
I will find that all available tasks are displayed, and I will
notice that they are divided into several types, with each
type having its specific tasks as follows:
1- Build tasks
For example, the assemble task has the function of building the
APK, and if I’m using flavors, it will generate all available APKs,
whether free, pro, or any other existing flavors.
2- Build Setup tasks
This usually refers to the tasks that prepare the build
environment or configurations before the project's build
process itself. This may include dependencies,
configurations, and others.
In this task, lint is responsible for outputting all existing issues in the
project, but in the form of HTML files. If I run this command and
then open the build file under reports, I will find an HTML file that
contains all the issues in the project along with their explanations.
4- Install tasks
These tasks are responsible for deploying and installing the APK.
5- Android tasks
6- Help tasks
Here's a small task whose only function is to print "hello world." Now,
we want to understand everything in this code.
The register function is used to define that a new task will be created
and takes two parameters: the first is the task name, which I specified
to be task1, and the second is the task itself, meaning the action that
will be executed, which is println("hello world").
When I run it, I will find the output displayed as follows:
How to Run the Custom Task?
The custom task can be run via the terminal just as I
would run built-in tasks.