Gradle Tutorial for Android: Getting Started – Part 1

In this Gradle Build Script tutorial, you’ll learn the basic syntax in build.gradle files generated by Android Studio. You’ll also learn about gradlew tasks, different dependency management techniques, and how to add a new dependency to your app. By Ricardo Costeira.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Managing Dependencies

Build and run the starter project:

The starter project

This screen shows a user 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 app, called Picasso.

To get started with Picasso, add it as a dependency to your project.

There are a few different ways to manage dependencies on a project. Ideally, you’ll want to have them defined in one place and reuse them everywhere else. For a simple project such as this one, though, using Gradle’s extra properties is more than enough. However, as a project grows, it might make sense to use something more elaborate like buildSrc or version catalogs.

You’ll learn about all of them, starting with extra properties.

Using the ext Property

Some developers create a specific file for this, but you can also do it in the project-level’s build.gradle. Open that file, and at the top, above the plugins block, add:

buildscript {
    ext {
        compileSdk = 34
        minSdk = 23
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
        kotlin = "1.9.20"
        agp = "8.2.2"
        picasso = "2.8"
        appcompat = "1.6.1"
        material = "1.11.0"
    }
}

Here, you’re defining dependency versions, as well as some plugins and framework versions, through the ext (extra) property in buildscript.

Below, update the plugins like so:

id "com.android.application" version "$agp" apply false
id "org.jetbrains.kotlin.android" version "$kotlin" apply false

After this, update the module-level build.gradle accordingly:

plugins {
    id "com.android.application"
    id "kotlin-android"
}
// 1
android {
    namespace "com.kodeco.socializify"
    compileSdk rootProject.compileSdk

    defaultConfig {
        applicationId "com.kodeco.socializify"
        minSdkVersion rootProject.minSdk
        targetSdkVersion rootProject.targetSdk
        versionCode rootProject.versionCode
        versionName rootProject.versionName
    }

    buildFeatures {
        viewBinding true
    }

    kotlin {
        jvmToolchain(17)
    }
}
// 2
dependencies {
    implementation fileTree(include: ["*.jar"], dir: "libs")
    implementation "androidx.appcompat:appcompat:$appcompat"
    implementation "com.google.android.material:material:$material"
}

Here, you:

  1. Extract all the version-related values in the android block. You access the properties you defined earlier with the ext property through the rootProject property.
  2. Extract all the dependency versions. You access the properties by using the $version notation in the dependency import strings.

Sync the project, and the build should complete successfully. You’re done! Next, you’ll learn how to use buildSrc.

Using buildSrc

As a project grows, managing dependencies with ext can become quite cumbersome and complex — especially if your app has multiple modules. With buildSrc you get rid of some of that complexity, although it requires a bit of initial setup.

buildSrc is a special directory that Gradle treats as an included build. In other words, buildSrc has its own build, but Gradle includes it in your app’s build process. Gradle will compile buildSrc first, and use any dependencies declared in it to resolve your app’s dependencies. Exactly what you’re looking for!

The standard way to leverage buildSrc is by using Kotlin. So it’s time for some renaming. In the project, rename the Gradle files you changed so far to groovy-[build|settings].gradle. Then, remove the kotlin- prefix from the .gradle.kts files. In the end, your project structure will look like this:

Time for some Kotlin DSL!

Sync the project by clicking on the little elephant on the top right corner:

Click to sync the project

Now, to create buildSrc. Right-click on SocialzifyStarter, and in the menu choose New, then Directory. In the prompt, write buildSrc.

Creating buildSrc

Right-click on your new buildSrc directory and select New, then File, and create a file called build.gradle.kts. To use Kotlin DSl with buildSrc, you need to enable it. In this new build.gradle.kts file, add:

plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}

You need the repository here to resolve the dependency on the JDK. Remember, this is an independent build from your app, even if it’s included in the end. Sync the project, and it should build successfully.

The last step is to create the file structure. Right-click buildSrc again. Select New, then Directory. Under the source set list, select src/main/java.

Creating buildSrc file structure

Now, you can code. You’ll create three different files inside the java package in order to have a nice separation of concerns. First, create AppConfig.kt. In it, create the AppConfig object:

object AppConfig {
    const val compileSdk = 34
    const val minSdk = 23
    const val targetSdk = 34
    const val versionCode = 1
    const val versionName = "1.0"
}

Next, create Versions.kt with the following Versions object:

object Versions {
    const val kotlin = "1.9.20"
    const val agp = "8.2.2"
    const val appcompat = "1.6.1"
    const val material = "1.11.0"
    const val picasso = "2.8"
}

Finally, create Dependencies.kt, with the Dependencies object:

object Dependencies {
    const val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
    const val material = "com.google.android.material:material:${Versions.material}"
    const val picasso = "com.squareup.picasso:picasso:${Versions.picasso}"
}

That’s all. Pretty self-explanatory, as you’re just creating properties to hold app/framework configuration, dependency versions, and the dependencies themselves. All that’s left to do now is use it!

In the project-level build.gradle.kts, replace the plugins with:

id("com.android.application") version Versions.agp apply false
id("org.jetbrains.kotlin.android") version Versions.kotlin apply false

And in the module-level build.gradle.kts replace the values in the android block:

...
compileSdk = AppConfig.compileSdk

defaultConfig {
    applicationId = "com.kodeco.socializify"
    minSdk = AppConfig.minSdk
    targetSdk = AppConfig.targetSdk
    versionCode = AppConfig.versionCode
    versionName = AppConfig.versionName
}
...

And in the dependencies block, replace the current ones with:

implementation(Dependencies.appcompat)
implementation(Dependencies.material)

Using Kotlin, you also get syntax highlighting and autocomplete. Sync the project, and it should build successfully. You now have a working buildSrc system. One thing to keep in mind is that due to the way Gradle handles it, a change in buildSrc will force your whole project to recompile, which can lead to a downgrade in build performance.

Fortunately, the final and third refactor of your dependency management system offers similar capabilities without the performance hit.

Using Version Catalogs

Version catalogs are the most recent iteration on Gradle dependency management. They allow you to maintain your dependencies and plugins in complex projects in a clean and scalable way. All the modules in your app can refer to this so-called catalog and fetch their dependencies in a type-safe way. Plus, you can use it with both Groovy and Kotlin.

Version catalogs also require some initial configuration. Locate the gradle folder on the root project, right-click it, select New, then File, and create
libs.versions.toml.

Creating the .toml file

.toml file in the gradle folder

libs.version.toml is a special name that Gradle uses to locate this file in the gradle folder. You’ll put all the information regarding versions and dependencies here. It also has a specific syntax. To add the versions, open the file and fill it with this code:

# 1
[versions]

# 2
minSdk = "23"
compileSdk = "34"
targetSdk = "34"
versionCode = "1"
versionName = "1.0"

kotlin = "1.9.20"
agp = "8.2.2"
picasso = "2.8"
appcompat = "1.6.1"
material = "1.11.0"

Here, you:

  1. Add the versions section to the file. The file can only have up to four sections: versions, plugins, bundles, and libraries.
  2. Add the version values. Since you’re limited to the four sections, there’s really no other place to put things like minSdk or targetSdk.

Now, below this code, add the libraries section:

[libraries]
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
picasso = { group = "com.squareup.picasso", name = "picasso", version.ref = "picasso" }

Here, you’re partitioning the dependencies at the : delimiter and referencing the corresponding version property. For instance, appcompat:

// Went from
implementation("androidx.appcompat:appcompat:1.6.1")

// To
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }

Partitioning is not strictly necessary — there are other ways to include the dependencies — but it keeps dependencies cleaner.

The last section you need is the plugins one. Add it below the ones you already have:

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

This is all for this file. Sync the project, and the versions catalog is ready for use. Go to the project-level build.gradle(.kts), and update the plugins:

alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false

Here, you call the alias function, passing in the plugins you want to include, accessing them through the accessors that Gradle automatically creates from the catalog. For instance, notice how Gradle picked the dots in the "com.android.application" string and made it so that you can access it like libs.plugins.android.application.

Next, the module-level Gradle script. If you’re using build.gradle, update the plugins block accordingly:

plugins {
    alias libs.plugins.android.application
    alias libs.plugins.kotlin.android
}

After that, replace the values in the android block with:

...
compileSdk libs.versions.compileSdk.get().toInteger()

defaultConfig {
    applicationId "com.kodeco.socializify"
    minSdk libs.versions.minSdk.get().toInteger()
    targetSdk libs.versions.targetSdk.get().toInteger()
    versionCode libs.versions.versionCode.get().toInteger()
    versionName libs.versions.versionName.get()
}
...

Or, if you’re using its build.gradle.kts counterpart, update the plugins block:

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}

And the values in the android block:

...
compileSdk = libs.versions.compileSdk.get().toInt()

defaultConfig {
    applicationId = "com.kodeco.socializify"
    minSdk = libs.versions.minSdk.get().toInt()
    targetSdk = libs.versions.targetSdk.get().toInt()
    versionCode = libs.versions.versionCode.get().toInt()
    versionName = libs.versions.versionName.get()
}
...

In case you start needing these in more modules, you can use version catalogs together with Gradle’s convention plugins to include it all in a plugin and reuse it. But that’s out of this tutorial’s scope.

The last thing to do here is to change the dependencies block below. For Kotlin, replace the dependencies with:

implementation(libs.appcompat)
implementation(libs.material)

For Groovy, use the following:

implementation libs.appcompat
implementation libs.material

Sync, and you’re done!

You’re using almost everything in the libs.versions.toml file, except for bundles. They are used to group dependencies, which can be quite useful. For instance, say you wanted to include Android’s Navigation Component in the project. Typically, you need two dependencies:

[versions]
// ...
navigation = "2.6.0

[libraries]
// ...
navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" }
navigation-ui = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" }

Instead of including both of them everywhere you need, you could declare a bundle:

[bundles]
navigation = ["navigation-fragment", "navigation-ui"]

This way, you’d only need to declare the following under the dependencies block. With Kotlin:

dependencies {
  // ...
  implementation(libs.bundles.navigation)
}

Or with Groovy:

dependencies {
  // ...
  implementation libs.bundles.navigation
}

And Gradle would automatically add all the libraries in the bundle as dependencies.