Tool and library interdependencies

Build dependencies are external components required to successfully build your project. A build can depend on libraries, plugins, subprojects, the Android SDK, tooling such as Kotlin and Java compilers, development environments like Android Studio, and Gradle itself.

Each dependency can itself require other dependencies. We call these transitive dependencies, and can rapidly increase the overall dependencies used by your application. When you want to upgrade a dependency, whether it's a library, tool, or the Android SDK, that upgrade can cascade, upgrading many other dependencies.

Often, this will cause no pain, as many libraries follow a scheme known as Semantic Versioning. These libraries restrict the types of changes they make to provide compatibility with their lower versions.

Semantic versioning follows a major.minor.patch format. For example, in the version number 4.8.3, 4 is the major version, 8 is the minor version and 3 is the patch number. When the major part changes, the library might have breaking changes in API or behavior. This can impact your build or application behavior.

When the minor (new features) or patch (bug fixes) parts change, the library developers are telling you that the library is still compatible and shouldn't impact your application.

It's important to watch for such changes, and several Dependency upgrade tools can assist.

Relationships in your build

Android builds contain relationships between:

  • Source code - the code and resources that you have control over
  • Library dependencies - external libraries or modules that your project and subprojects include when building
  • Tools - compilers, plugins, and SDKs that translate your source into an application or library
Build dependencies and their relationships
Figure 1. Build relationships

Source code

Your source code is Kotlin or Java code that you write in your application or library. (For details on using C++, see Android NDK.)

Source code depends on libraries (including Kotlin and Java runtime libraries) and the Android SDK, and requires its corresponding Kotlin or Java compiler.

Some source code includes annotations that require additional processing. For example, if you're writing Jetpack Compose code, you add annotations such as @Composable that need to be processed by the Compose Kotlin compiler plugin. Other annotations may be processed by a Kotlin Symbol Processor (KSP) or separate annotation-processing tools.

Library dependencies

Libraries contain bytecode pulled in as part of your application. This could be a Java JAR, Android library (AAR), or a subproject in your build. Many libraries follow Semantic Versioning, which can help you understand when they remain compatible (or not) when upgraded.

Libraries may depend on other libraries for reuse, called a transitive dependency. This reduces the dependencies that you must explicitly manage; you specify the dependencies that you directly use, and Gradle pulls them in along with those transitive dependencies. Be aware that as you upgrade your direct dependencies, they may upgrade those transitive dependencies.

Sometimes a library may require minimum versions of the Android SDK at runtime (minSdk) or compile time (compileSdk). This is necessary when a library uses functions included in the Android SDK or its provided JDK APIs. The effective minSdk of your application is the highest minSdk requested by your application and all of its direct and transitive library dependencies.

Using some libraries may require use of a specific Gradle plugin. These helper plugins often install Kotlin Symbol Processors or other annotation processors that generate code or modify compilation of your source to support your use of library features. For example, Jetpack Room includes annotations and a KSP that transforms them into generated code to retrieve and modify data in a database. Jetpack Compose requires the Compose compiler plugin to modify annotated functions to manage how and when that function is re-run.

Tools

Gradle

Gradle is the build tool that reads your build files and generates your application or library, and also exposes an API for plugins to extend its capabilities. Gradle runs multiple processes on one or more Java virtual machines, and its Java plugins call the Java tooling inside the JDK.

Gradle plugins

Gradle plugins extend Gradle by defining new tasks and configuration. Applying a plugin to your build enables specific build capabilities, configured as data in your build scripts. For Android builds, the most important Gradle plugin is the Android Gradle Plugin (AGP).

Compilers

The Kotlin or Java compiler transforms your source code into executable bytecode. The Kotlin compiler exposes a plugin API that enables external analysis and code generation to be run directly inside the compiler, accessing parsed code structure.

Compiler plugins

Compiler plugins perform analysis and code generation inside the Kotlin compiler while the Kotlin compiler is analyzing your code, and are installed when you apply their Gradle plugins to the build.

Android SDK

The Android SDK contains the Android Platform and Java APIs for a specific version of Android, and its corresponding tools. These tools help you manage the SDK, build your applications, and communicate with and emulate Android devices.

Each version of the Android SDK provides specific Java APIs that your source code can access, and desugaring support to use those APIs on earlier versions of Android.

JDK

The Java Development Kit, containing Java libraries and executables to compile Java source and run Java applications. There are several JDKs at play in an Android build. See Java versions in Android builds for more details.

Gradle scopes

Gradle groups library dependencies into different scopes (called configurations in the Gradle API), allowing you to specify different sets of library dependencies to be used in different parts of your build. For example, you probably don't want to include test libraries such as JUnit in your published application or library, but you do want them when building and executing your unit tests. You also use scopes to add symbol or annotation processors to analyze your code.

For example, AGP defines implementation and api scopes, your way of specifying whether a dependency should be exposed to users of your subproject. See Configure dependencies for descriptions of these and other scopes used in an Android build.

Add library dependencies in your build files' dependencies block, either as group:artifact:version strings:

Kotlin

// In a module-level build script
// explicit dependency strings ("group:artifact:version")
dependencies {
    implementation("com.example:library1:1.2.3")
    api("com.example:library2:1.1.1")
}

Groovy

// In a module-level build script
// explicit dependency strings ("group:artifact:version")
dependencies {
    implementation 'com.example:library1:1.2.3'
    api 'com.example:library2:1.1.1'
}

or in a Version Catalog:

# Version catalog - gradle/libs.versions.toml
[versions]
exampleLib = "1.2.3"
examplePlugin = "2.3.4"

[libraries]
example-library = { group = "com.example", name = "library", version.ref = "exampleLib" }

[plugins]
example-plugin = { id = "com.example.plugin", version.ref = "examplePlugin" }

and specify the generated variables in your build files:

Kotlin

// In a module-level build script
// Using a version catalog
plugins {
    alias(libs.plugins.example.plugin)
}

dependencies {
    implementation(libs.example.library)
}

Groovy

// In a module-level build script
// Using a version catalog
plugins {
    alias(libs.plugins.example.plugin)
}

dependencies {
    implementation libs.example.library
}