Gradle Tutorial for Android: Getting Started

Irina Galata

Gradle and AndroidOne of the vital parts of creating an app, along with the development itself, is the process of building – putting together the modules and specifying the interactions between them. The primary goal of build automation systems is to make this process more convenient and efficient for a developer. Gradle is an excellent example of such a tool, and is the primary build tool for Android.

In this tutorial, you’ll gain a better understanding of what Gradle is, and how you can use Gradle to supercharge your builds. By the end of this tutorial you should be able to

  1. Build your Android apps from the command-line
  2. Read through a Gradle build file
  3. Create your own Gradle plugin
  4. Create build flavors for profit!
Note: This tutorial assumes you’re already familiar with the basics of Android development. If you are completely new to Android development, read through our Beginning Android Development tutorials to familiarize yourself with the basics.

What is Gradle?

Gradle is an open source build automation system. It brings the convenience of a Groovy-based DSL along with the advantages of Ant and Maven. With Gradle, you can easily manipulate the build process and its logic to create multiple versions of your app. It’s much easier to use and a lot more concise and flexible when compared to Ant or Maven alone.

So, there was little wonder why during Google I/O in May 2013, the Android Gradle plugin was introduced as the build tool built into the first preview of Android Studio :]

Getting Started

Download SocializifyStarter, the starter project for this tutorial. At minimum, you’ll need Android Studio 3.0 installed on your computer. Open the project in Android Studio, and you’ll be prompted to setup the Gradle wrapper:

Setup Gradle wrapper

Choose OK to configure the wrapper, which you’ll learn more about later in the tutorial.

Depending on which version of Android Studio you’re running, you may also be prompted to update the Gradle plugin:

Update gradle plugin

Choose Update to finish opening the project in Android Studio.

Before starting working with the project, let’s review its structure in the Project pane in Android Studio:

Project structure

Pay attention to the files with the green Gradle icon and .gradle extension. These files are generated by Android Studio automatically during project creation. They are responsible for the processing of your project’s build. They contain the necessary info about the project structure, library dependencies, library versions, and the app versions you’ll get as a result of the build process.

Project-level build.gradle

Find the build.gradle file in the root directory of the project. It’s called a top-level (project-level) build.gradle file. It contains the settings which are applied to all modules of the project.

Note: If you’re unfamiliar with modules, checkout out our second tutorial on Android studio found here.
// 1
buildscript {
    // 2
    repositories {
    // 3
    dependencies {
        classpath ''
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.51'

// 4
allprojects {
    repositories {

Here’s what’s going on, step by step:

  1. In the buildscript block you define settings needed to perform your project building.
  2. In the repositories block you add names of the repositories that Gradle should search for the libraries you use.
  3. The dependencies block contains necessary plugin dependencies, in this case the Gradle and Kotlin plugins. Do not put your module dependencies in this block.
  4. The structure of the allprojects block is similar to the buildscript block, but here you define repositories for all of your modules, not for Gradle itself. Usually you don’t define the dependencies section for allprojects. The dependencies for each module are different and should reside in the module-level build.gradle.

Module-level build.gradle

Now go to the build.gradle file in the app module directory. It contains dependencies (libraries which a module relies on), and instructions for the build process. Each module defines its own build.gradle file.

// 1
apply plugin: ''
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

// 2
android {
    // 3
    compileSdkVersion 27
    // 4
    buildToolsVersion "26.0.2"
    // 5
    defaultConfig {
        // 6
        applicationId "com.raywenderlich.socializify"
        // 7
        minSdkVersion 21
        // 8
        targetSdkVersion 27
        // 9
        versionCode 1
        // 10
        versionName "1.0"

// 11
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.51'
    implementation ''
    implementation ''

The code above does the following:

  1. Specifies a list of plugins needed to build the module. The plugin is necessary in order to setup the Android-specific settings of the build process. Here you can also use if you’re creating a library module. The kotlin-android and kotlin-android-extensions plugins allow you to use the Kotlin language and the Kotlin Android extensions in your module.
  2. In the android block you place all platform-specific options of the module.
  3. The compileSdkVersion option indicates the API level your app will be compiled with. In other words, you cannot use features from an API higher than this value. Here, you’ve set the value to use APIs from Android Oreo.
  4. The buildToolsVersion option indicates the version of the compiler. From Gradle plugin 3.0.0 onward, this field is optional. If it is not specified, the Android SDK uses the most recent downloaded version of the Build Tools.
  5. The defaultConfig block contains options which will be applied to all build versions (e.g., debug, release, etc) of your app by default.
  6. The applicationId is the identifier of your app. It should be unique so as to successfully publish or update your app on Google Play Store.
  7. In order to set the lowest API level supported, use minSdkVersion. Your app will not be available in the Play Store for the devices running on lower API levels.
  8. Note: To get more acquainted with the Android SDK versions read our tutorial covering that topic
  9. The targetSdkVersion parameter defines the maximum API level your app has beeen tested on. That is to say, you’re sure your app works properly on the devices with this SDK version, and it doesn’t require any backward compatibility behaviors. The best approach is to thoroughly test an app using the latest API, keeping your targetSdkVersion value equal to compileSdkVersion.
  10. versionCode is a numeric value for the app version.
  11. versionName is a user-friendly string for the app version.
  12. The dependencies block contains all dependencies needed for this module. Later in this tutorial, you’ll find out more about managing your project’s dependencies.

Finally, settings.gradle

Whew, build.gradle was quite a big file! Hope, you’re not tired yet :] The next file will be quite short – move to the settings.gradle file in the root directory. Its contents should look as follows:

include ':app'

In this file, you should define all of your project’s modules by name. Here we have only one module – app. In a large, multi-module project, this file can have a much longer list.

Groovy vs. Kotlin in Gradle

Talk about Kotlin

Kotlin’s popularity is growing every day. Besides Android apps, you can also write back-end web code, front-end web code, and even iOS apps using Kotlin! Recently, Gradle announced Kotlin language support for writing build scripts. The Gradle Kotlin DSL is still in pre-release and requires nontrivial setup, and won’t be covered in this tutorial. However, it’s quite promising and surely worth waiting for its release.

Why Kotlin

You may be wondering, why would you use Kotlin for writing Gradle scripts?

First of all, Kotlin is a statically typed language (Groovy is dynamically typed), which allows for conveniences like autocompletion, better refactoring tools and source-code navigation. You can work in script files just you would with Kotlin classes, with all support of Android Studio you’re used to. Moreover, autocompletion will prevent you from making typos :].

Secondly, it’s practical to work with a single language across your app and your build system.

Mastering the build: Gradle Commands

To execute Gradle commands, you can use both the command line and Android Studio. It’s better to start from the former one to get acquainted more deeply about what’s going on. So, how can you start working with Gradle commands? Pretty easy – use gradlew.

What is gradlew

gradlew is the Gradle Wrapper. You don’t need to worry about installating Gradle on your computer – the wrapper will do that for you. Even more, it’ll allow you to have different projects built with various versions of Gradle.

Open your command line and move to the root directory of the starter project:

cd path/to/your/Android/projects/SocializifyStarter/

gradlew tasks

After that, execute the following command:

./gradlew tasks

You’ll see a list containing all available tasks:

> Task :tasks

All tasks runnable from root project

Android tasks
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.

Build tasks
assemble - Assembles all variants of all applications and secondary packages.
assembleAndroidTest - Assembles all the Test applications.
assembleDebug - Assembles all Debug builds.
assembleRelease - Assembles all Release builds.

Build Setup tasks
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Help tasks

Install tasks

Verification tasks
lint - Runs lint on all variants.

To see all tasks, run gradlew tasks --all

To get more detail about task, run gradlew help --task <task>

These commands exist to help you with tasks like project initialization, building, testing and analyzing. If you forget a specific command, just execute ./gradlew tasks to refresh your memory.

gradlew assemble

Now skim the list of commands again, and find commands starting with ‘assemble’ under the Build tasks section. Run the first command:

./gradlew assemble

Below is the output of executing this command:

> Task :app:compileDebugKotlin
Using kotlin incremental compilation

> Task :app:compileReleaseKotlin
Using kotlin incremental compilation

52 actionable tasks: 52 executed

From the output, it’s apparent that Gradle compiled two versions of the app – debug and release.
Verify this by changing to the build output directory:

cd app/build/outputs/apk/

To review the contents of a directory run the following command:

ls -R

The ls command displays all files and directories in the current directory. The -R parameter forces this command to execute recursively. In other words, you’ll not only see the contents of your current directory but also of child directories.

You’ll get the following output:

debug	release

app-debug.apk	output.json

app-release-unsigned.apk	output.json

As you see, Gradle generated both debug and release apks.

gradlew lint

Move back to the root directory:

cd ../../../..

Run the following command:

./gradlew lint

The lint command, and any commands which start with ‘lint’, analyzes the whole project looking for various mistakes, typos or vulnerabilities. The first command will find all the issues in a project with both critical and minor severity.

You’ll get the output with the count of issues found:

> Task :app:lint
Ran lint on variant debug: 47 issues found
Ran lint on variant release: 47 issues found
Wrote HTML report to file:///Users/username/path/to/your/Android/projects/SocializifyStarter/app/build/reports/lint-results.html
Wrote XML report to file:///Users/username/path/to/your/Android/projects/SocializifyStarter/app/build/reports/lint-results.xml

Review the report by typing the following on Mac:

open app/build/reports/lint-results.html

or on Linux:

xdg-open app/build/reports/lint-results.html

The default browser on your computer will open with the specified file:

Lint issues 1

Lint issues 2

You can inspect all the issues found with code snippets and an expanded description of a possible solution. However, don’t focus too much on all of these issues – pay attention to the critical ones and fix them immediately. Minor issues shouldn’t necessarily warrant a refactoring, depending upon your teams guidelines and processes.

Managing Dependencies

Now it’s time to make changes to the application itself. Build and run the starter project:

The starter project

This screen shows a user’s profile – name, followers, photos, etc. However, something’s missing – an avatar! In order to load an avatar from a URL, we’ll use a third-party library in our application, namely, we’ll use Picasso.

Picasso is described as a “A powerful image downloading and caching library for Android”. To get started with Picasso, you need to add it as a dependency to your project.

First, create a file named dependencies.gradle in the root directory of the project. You’ll use this file as the means of identifying all the project dependency versions in one place. Add the following to this file:

ext {
    minSdkVersion = 17
    targetSdkVersion = 27
    compileSdkVersion = 27
    buildToolsVersion = "26.0.2"
    kotlinVersion = "1.1.51"
    supportVersion = "27.0.1"
    picassoVersion = "2.5.2"

Open the project-level build.gradle file (the one in the root directory, not the one in the app directory!) and add the following line on the top of the file:

apply from: 'dependencies.gradle'

Now you can use the properties you specified in the dependencies.gradle file in your other project build files like this:

app module-level build.gradle

android {
    compileSdkVersion rootProject.compileSdkVersion
    buildToolsVersion rootProject.buildToolsVersion
    defaultConfig {
        applicationId "com.raywenderlich.socializify"
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 1
        versionName "1.0"

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "$rootProject.supportVersion"
    implementation "$rootProject.supportVersion"
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$rootProject.kotlinVersion"

Add the following line, to include Picasso, in your module-level build.gradle file (in the app directory!) inside the dependencies block:

implementation "com.squareup.picasso:picasso:$rootProject.picassoVersion"

When you modify build files, you’ll be prompted to sync the project:

Gradle sync

Don’t be afraid to re-sync your project. It takes a short while until the sync is completed, and the time it takes gets longer when you have more dependencies and more code in your project.

When the project syncing is complete, open ProfileActivity and add this function to load a user’s avatar:

// 1
private fun loadAvatar() {
  // 2

If you get build errors, or if you’re prompted to resolve the imports in Android studio, be sure the following imports are included:

import com.squareup.picasso.Picasso

Here’s a step-by-step explanation of what’s going on:

  1. You define a Kotlin function loadAvatar()
  2. Picasso needs an instance of a Context to work, so you must call with(context: Context!), passing in the current Activity as the Context. This returns an instance of the Picasso class. Next, you specify the URL of an image you want to load with the load(path: String!) method. The only thing left is to tell Picasso where you want this image to be shown by calling the into(target: ImageView!) method.

Add this function invocation in the onCreate(savedInstanceState: Bundle?) function of your ProfileActivity:


The last step, which even senior Android developers tend to forget, is to add the android.permission.INTERNET permission. If you forgot to add this permission, Picasso simply can’t download the image and it’s hard to spot any error. Go to the AndroidManifest.xml file and add the following permission above the application tag:

<uses-permission android:name="android.permission.INTERNET" />

That’s all you need to show the user’s avatar. Build and run the project:

App with avatar

Gradle Dependency Configurations

The implementation keyword you previously used is a dependency configuration, which tells Gradle to add Picasso in such way, that it’s not available to other modules. This option significantly speeds up the build time. You’ll use this keyword more often than others.

In some other cases, you may want your dependency to be accessible to other modules of your project. In those cases, you can use the api keyword.

Other options include runtimeOnly and compileOnly configurations, which mark a dependency’s availability during runtime or compile time only.

Ready to Publish: Working with Product Flavors and Build Types

Your app is ready, and you’re thinking of ways to profit from it :]

Money money money

One solution might be to have multiple versions of your app: a free and paid version. Luckily for you, gradle supports this at the build level, allowing you to define the boundaries of different build types. However, before getting started, you need to understand how Gradle allows you to work with different app versions.

Build Types

By default, there are two build types – debug and release. The only difference between them is the value of the debuggable parameter. In other words, you can use the debug version to review logs and to debug the app, while the release one is used to publish your app to the Google Play Store. You can configure properties to the build types by adding the following code in the android block of your module-level build.gradle file:

buildTypes {
    release {

    debug {


In the debug and release blocks you can specify the type-specific settings of your application.

Build Signing

One of the most important configurations of the build is its signature. Without a signature, you’ll be unable to publish your application, since it’s necessary to verify you as an owner of the specific application. While you don’t need to sign the debug build – Android Studio does it automatically – the release build should be signed by a developer.

Note: To proceed you need to generate the keystore for your release build. Take a look at this tutorial to find a step-by-step guide

When your keystore is ready, add the code below in the android block and above the buildTypes block (the order of declaration matters) of the module-level build.gradle file:

signingConfigs {
    release {
        storeFile file("path to your keystore file")
        storePassword "your store password"
        keyAlias "your key alias"
        keyPassword "your key password"

In the signingConfigs block, you specify your signature info for the build types. Pay attention to the keystore file path. It should be specified with respect to the module directory. In other words, if you created a keystore file in the module directory and named it “keystore.jks”, the value you should specify will be equal to the name of the file.

Update the buildTypes block to sign your release build automatically:

release {
    signingConfig signingConfigs.release
Note: There are two important considerations related to your keystore file:

  1. Once you’ve published your app to the Google Play Store, subsequent submissions must use the same keystore file and password, so keep them safe.
  2. Be sure NOT to commit your keystore passwords to a version control system such as GitHub. You can do so by keeping the password in a separate file from build.gradle, say keystorePassword.gradle in a Signing directory, and then referencing the file from the app module-level build.gradle via:
    apply from: "../Signing/keystorePassword.gradle

Then be sure to keep keystorePassword.gradle ignored by your version control system. Other techniques include keeping the password in an OS-level environment variable, especially on your remote Continuous Integration system, such as CircleCI.

Build Flavors

In order to create multiple versions of your app, you need to use product flavors. Flavors are a way to differentiate the properties of an app, whether it’s free/paid, staging/production, etc.

You’ll distinguish your app flavors with different app names. First, add the following names as strings in the strings.xml file:

<string name="app_name_free">Socializify Free</string>
<string name="app_name_paid">Socializify Paid</string>

And remove the existing:

<string name="app_name">Socializify</string>

Now that the original app_name string is no longer available, edit your AndroidManifest.xml file and replace android:label="@string/app_name" with android:label="${appName}" inside the application tag.

Add the following code in the android block of your module-level build.gradle file:

// 1
flavorDimensions "appMode"
// 2
productFlavors {
    // 3
    free {
        // 4
        dimension "appMode"
        // 5
        applicationIdSuffix ".free"
        // 6
        manifestPlaceholders = [appName: "@string/app_name_free"]
    paid {
        dimension "appMode"
        applicationIdSuffix ".paid"
        manifestPlaceholders = [appName: "@string/app_name_paid"]
  1. You need to specify the flavor dimensions to properly match the build types. In this case, you need only one dimension – the app mode.
  2. In the productFlavors specify a list of flavors and their settings. In this case, free, and paid
  3. Specify the name of the first product flavor – free.
  4. It’s mandatory to specify the dimension parameter value. The free flavor belongs to the appMode dimension.
  5. Since you want to create separate apps for free and paid functionality, you need them to have different app identifiers. The applicationIdSuffix parameter defines a string that’ll be appended to the applicationId giving your app unique identifiers.
  6. The manifestPlaceholders allows you to modify properties in your AndroidManifest.xml file at build time. In this case, modify the application name depending on its version.

Sync your project with Gradle again. After the project sync, run the tasks command, and see if you can spot what’s changed:

./gradlew tasks

You’ll get a similar list of tasks to the one you got when you ran this command first time:

Build tasks
assembleDebug - Assembles all Debug builds.
assembleFree - Assembles all Free builds.
assemblePaid - Assembles all Paid builds.
assembleRelease - Assembles all Release builds.

Spot the difference? If you pay attention to the tasks under the Build tasks section, you should have some new ones there. You now have separate commands for each build type and build flavor.

Remove the generated output folder from previous ./gradlew assemble task so that you can see the clear difference before and after adding buildTypes and productFlavors. Run the command:

rm -rf app/build/outputs/apk


./gradlew assembleDebug

When the command completes, check the output directory:

cd app/build/outputs/apk
ls -R

You’ll get something like this:

free   paid


app-free-debug.apk   output.json


app-paid-debug.apk   output.json

You should have two builds generated – freeDebug and paidDebug.

What is a Build Variant

From the output above, what you’ve actually generated are different build variants, which are a combination of build types – debug and release and build flavors – free and paid. That is to say, you have four possible build variants – paidDebug, paidRelease, freeDebug and freeRelease.

Great! You’ve got two different build flavors, however, differing names isn’t enough for you to profit from. Instead, you’ll configure your app’s behavior based on the flavor type!

Declare a constant for the paid flavor right below the declaration of ProfileActivity class:

companion object {
  const val PAID_FLAVOR = "paid"

Add the following function to ProfileActivity:

private fun isAppPaid() = BuildConfig.FLAVOR == PAID_FLAVOR

You can now check if a user is using a paid version of the app. Depending on the result of this check, you’ll enable or disable some functionality visible to your user so they can clearly see what version they’re using in-app.

Add these strings to the strings.xml file:

<string name="free_app_message">Hi! You\'re using the free version of the application</string>
<string name="paid_app_message">Hi! Congratulations on buying
        the premium version of the application</string>

Add the following functions below isAppPaid():

private fun showMessage() {
  val message = if (isAppPaid()) R.string.paid_app_message else R.string.free_app_message
  Toast.makeText(this, message, Toast.LENGTH_LONG).show()

private fun togglePhotosVisibility() {
  extraPhotos.visibility = if (isAppPaid()) View.VISIBLE else View.GONE
  restriction.visibility = if (isAppPaid()) View.GONE else View.VISIBLE

Add these functions invocations in the onCreate(savedInstanceState: Bundle?) function:


Now, your user will see a different greeting message and will be able to view the whole photo feed or just some of the photos depending on the app version.

Select the freeRelease build variant in the window below:

Build variant panr

Build and run the project (you may first need to choose the app build configuration in the drop-down next to the Run button):

Free app

You should see that the functionality of the app is restricted and the message with a corresponding text is shown.

Select the paidRelease option, and run the app again:

Paid app

If a user buys your app, they’ll be able to access its full functionality.

Creating Tasks

Sometimes you need your build system to do something more complicated or customize the build process in some way. For example, you may want Gradle to output an APK file containing the build date in its name. One possible solution to this is to create a custom Gradle task.

Add the following code in your module-level build.gradle file at the same level as android block:

// 1
task addCurrentDate() {
    // 2
    android.applicationVariants.all { variant ->
        // 3
        variant.outputs.all { output ->
            // 4
            def date = new Date().format("dd-MM-yyyy")
            // 5
            def fileName = + "_" + date + ".apk"
            // 6
            output.outputFileName = fileName

Here’s what’s is going on:

  1. You define an addCurrentDate() task.
  2. You iterate through all the output build variants.
  3. You iterate over all the APK files.
  4. You create an instance of Date and format it.
  5. You create a new filename appending the current date to the initial name.
  6. You set the new filename to current APK file.

Now you need to execute this task at a specific point of the build process. Add the following code below the task addCurrentDate() block:

gradle.taskGraph.whenReady {

The task specified in the whenReady block will be called once when the current graph is filled with tasks and ready to start executing them. Here, you specify the name of your addCurrentDate task.

Now, go back to the command line and make sure you’re in the root directory. Run the following command to assemble a build:

./gradlew assemblePaidRelease

After the task has completed, go to the output directory and check if the build has been named correctly:

cd app/build/outputs/apk/paid/release/

You should get a similar output:

output.json paidRelease_12-11-2017.apk

If your task executed correctly, all your builds will be named with this convention.

Creating Custom Plugins

Usually it’s a good idea to factor out your code into smaller pieces so it can be reused. Similarly, you can factor out your tasks into a custom behavior for the building process as a plugin. This will allow you to reuse the same behavior in other modules you may add to your project.

To create a plugin, add the following class below the addCurrentDate task in the module-level build.gradle file:

class DatePlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('addCurrentDatePluginTask') {
   { variant ->
                variant.outputs.all { output ->
                    def date = new Date().format("dd-MM-yyyy")
                    def fileName = + "_" + date + ".apk"
                    output.outputFileName = fileName

Add the name of your plugin at the top of this file along with the other apply plugin definitions:

apply plugin: DatePlugin

Conceptually, the code in the plugin is doing the same thing as the task – you’re still modifying the names of the output files. The only difference is that you define a class which implements Plugin and its single method apply(Project project).

In this method, you’re adding your plugin to the target – Project. By calling the task(String name, Closure configureClosure) method you’re creating a new task with a specific name and behavior and adding it to the project.

Now modify the whenReady block to call a new task:

gradle.taskGraph.whenReady {

and remove the task addCurrentDate() block you added earlier.

Now you can verify that this plugin is doing the same thing like the task. Assemble a new build and verify the APK filename:

./gradlew assemblePaidRelease
cd app/build/outputs/apk/paid/release/

output.json paidRelease_12-11-2017.apk

Where to Go From Here

You can download the final project here.

The Android Gradle plugin 3.0 contains some significant differences from previous versions. So it’s worth reviewing the changelog.

Also, if you’re insterested in the Gradle Kotlin DSL, here you can find a list of usage examples to get familiar with it.

I hope you’ve enjoyed this Getting Started with Gradle tutorial! Don’t forget to leave your feedback and feel free to ask any questions in the comments below :]


Each tutorial at is created by a team of dedicated developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Irina Galata

Software developer in Dnipro, Ukraine. She is passionate about Android, animations, public speeches and Kotlin.

You can find me on Medium and GitHub

Other Items of Interest Weekly

Sign up to receive the latest tutorials from each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 26 total!

iOS Team

... 80 total!

Android Team

... 35 total!

Unity Team

... 16 total!

Articles Team

... 4 total!

Resident Authors Team

... 29 total!

Podcast Team

... 7 total!

Recruitment Team

... 9 total!