Gradle dependency management

gradle logo

Gradle Dependency Management

If you are using single module architecture in your project, there’s no problem. But multi-module Android projects are the primary way to develop an Android App now, and it’s highly recommended to use this way to take advantage of performance improvements with Android Gradle Plugin (AGP) 3+. However, when the module size increases, we encounter a big issue - dependency management in each module.

Here we are going to introduce some solutions for Gradle dependency management.

Manual management

Not a recommended way to do, you might see there are many duplicated dependency code in different modules, for example:

Module_A/build.gradle

1
2
3
4
5
implementation "com.android.support:support-annotations:27.0.2"
implementation "com.android.support:appcompat-v7:27.0.2"
implementation "com.squareup.retrofit2:retrofit:2.3.0"
implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
implementation "io.reactivex.rxjava2:rxjava:2.1.9"

Module_B/build.gradle

1
2
3
4
5
implementation "com.android.support:support-annotations:27.0.2"
implementation "com.android.support:appcompat-v7:27.0.2"
implementation "com.squareup.retrofit2:retrofit:2.3.0"
implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
implementation "io.reactivex.rxjava2:rxjava:2.1.9"

This is not a good way and is painful. You will need to dive into different modules and copy-paste almost the same dependencies for each module.

Google Ext Section

This is the way recommended by Google. First of all, we need to define a project-level build.gradle and put the version and library information inside.

Project-level build.Gradle

1
2
3
4
5
6
7
8
buildscript {...}
allprojects {...}

// The following are only a few examples of the types of properties you can define.
extra["sdkVersion"] = 28
extra["supportLibVersion"] = "28.0.0"

...

After that, we need to define the module-level build.gradle file,

1
2
3
4
5
6
7
8
9
10
11
12
android {  
// Use the following syntax to access properties you define at the project level: // rootProject.ext.property_name
val sdkVersion: Int by rootProject.extra
  compileSdkVersion(sdkVersion)
...
}

...

dependencies {
implementation("com.android.support:appcompat-v7:${rootProject.extra["supportLibVersion"]}") ...
}

By using this way, it’s very convenient to manage all dependencies in a single file. That’s a considerable improvement.

Kotlin buildSrc

When I was at Cruse, we switched our dependency management to this way. You need to create a buildSrc module with Kotlin code to manage dependencies. The most significant benefit you can gain is leveraging the IDE completion support when importing a dependency.

So, here are steps to use this way.

  1. Create a buildSrc module

  2. Create build.gradle.kts file to enable this feature.

    1
    2
    3
    plugins {
    `kotlin-dsl`
    }
  3. Create your dependency file, for example, ProjectDependency.kt

1
2
3
4
5
6
7
object Versions {
val retrofit = "x.y.z"
}

object Libs {
val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
}

After creating those files, we can start using those defined dependencies in our module-level build.gradle files.

Gradle Version Catalog

Based on the Gradle document, central declaration of dependencies is an incubating feature. It requires the activation of the VERSION_CATALOGS :

1
enableFeaturePreview('VERSION_CATALOGS')

Here is an example to show how to define your version catalog:

settings.gradle.kts

1
2
3
4
5
6
7
8
9
10
11
12
13
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
alias("groovy-core").to("org.codehaus.groovy:groovy:3.0.5")
alias("groovy-json").to("org.codehaus.groovy:groovy-json:3.0.5")
alias("groovy-nio").to("org.codehaus.groovy:groovy-nio:3.0.5")
alias("commons-lang3").to("org.apache.commons", "commons-lang3").version {
strictly("[3.8, 4.0[")
prefer("3.9")
}
}
}
}

Based on this example, you can find the rules as following:

1
2
3
4
5
6
7
8
9
10
dependencyResolutionManagement {  
versionCatalogs {
create("libs") {
version('version name', 'version code')
alias('alias-sample').to('group', 'artifact').versionRef('version name')
alias('alias.sample').to('group', 'artifact').versionRef('version name')
bundle('bundle-set', ['alias-sample', 'alias.sample'])
}
}
}

It enables you to define your alias and bundle at the top level. You can use them in different modules like, libs.alias.sample or bundle.bundle.set . Version catalogs support autocompletion in the IDE through their type-safe accessors generated by Gradle.

Gradle also supports TOML for the version catalog feature. You would need to create a file called libs.versions.toml in the gradle subdirectory of the root build, then a catalog will be automatically declared with the contents of this file. The TOML file consists of 4 major sections:

  • the [versions] section is used to declare versions that dependencies can reference
  • the [libraries] section is used to declare the aliases to coordinates
  • the [bundles] section is used to declare dependency bundles
  • the [plugins] section is used to declare plugins

Here is an example for this file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[versions]
groovy = "3.0.5"
checkstyle = "8.37"

[libraries]
groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } }

[bundles]
groovy = ["groovy-core", "groovy-json", "groovy-nio"]

[plugins]
jmh = { id = "me.champeau.jmh", version = "0.6.5" }

The usage is very similar when you define those parameters in your gradle files. For example,

1
2
3
4
5
6
7
8
9
10
11
12
// access version section 
buildscript {
dependencies {
classpath "lib.x.y.z:${libs.versions.groovy.get()}"
}
}

// access a single dependency
implementation libs.groovy.core

// access a bundle
implementation libs.bundles.groovy

That’s it. Enjoy the coding.

#Android #Android/Gradle/VersionCatalog

Gradle dependency management

gradle logo

Gradle Dependency Management

If you are using single module architecture in your project, there’s no problem. But multi-module Android projects are the primary way to develop an Android App now, and it’s highly recommended to use this way to take advantage of performance improvements with Android Gradle Plugin (AGP) 3+. However, when the module size increases, we encounter a big issue - dependency management in each module.

Here we are going to introduce some solutions for Gradle dependency management.

Manual management

Not a recommended way to do, you might see there are many duplicated dependency code in different modules, for example:

Module_A/build.gradle

1
2
3
4
5
implementation "com.android.support:support-annotations:27.0.2"
implementation "com.android.support:appcompat-v7:27.0.2"
implementation "com.squareup.retrofit2:retrofit:2.3.0"
implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
implementation "io.reactivex.rxjava2:rxjava:2.1.9"

Module_B/build.gradle

1
2
3
4
5
implementation "com.android.support:support-annotations:27.0.2"
implementation "com.android.support:appcompat-v7:27.0.2"
implementation "com.squareup.retrofit2:retrofit:2.3.0"
implementation "com.squareup.retrofit2:adapter-rxjava2:2.3.0"
implementation "io.reactivex.rxjava2:rxjava:2.1.9"

This is not a good way and is painful. You will need to dive into different modules and copy-paste almost the same dependencies for each module.

Google Ext Section

This is the way recommended by Google. First of all, we need to define a project-level build.gradle and put the version and library information inside.

Project-level build.Gradle

1
2
3
4
5
6
7
8
buildscript {...}
allprojects {...}

// The following are only a few examples of the types of properties you can define.
extra["sdkVersion"] = 28
extra["supportLibVersion"] = "28.0.0"

...

After that, we need to define the module-level build.gradle file,

1
2
3
4
5
6
7
8
9
10
11
12
android {  
// Use the following syntax to access properties you define at the project level: // rootProject.ext.property_name
val sdkVersion: Int by rootProject.extra
  compileSdkVersion(sdkVersion)
...
}

...

dependencies {
implementation("com.android.support:appcompat-v7:${rootProject.extra["supportLibVersion"]}") ...
}

By using this way, it’s very convenient to manage all dependencies in a single file. That’s a considerable improvement.

Kotlin buildSrc

When I was at Cruse, we switched our dependency management to this way. You need to create a buildSrc module with Kotlin code to manage dependencies. The most significant benefit you can gain is leveraging the IDE completion support when importing a dependency.

So, here are steps to use this way.

  1. Create a buildSrc module

  2. Create build.gradle.kts file to enable this feature.

    1
    2
    3
    plugins {
    `kotlin-dsl`
    }
  3. Create your dependency file, for example, ProjectDependency.kt

1
2
3
4
5
6
7
object Versions {
val retrofit = "x.y.z"
}

object Libs {
val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
}

After creating those files, we can start using those defined dependencies in our module-level build.gradle files.

Gradle Version Catalog

Based on the Gradle document, central declaration of dependencies is an incubating feature. It requires the activation of the VERSION_CATALOGS :

1
enableFeaturePreview('VERSION_CATALOGS')

Here is an example to show how to define your version catalog:

settings.gradle.kts

1
2
3
4
5
6
7
8
9
10
11
12
13
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
alias("groovy-core").to("org.codehaus.groovy:groovy:3.0.5")
alias("groovy-json").to("org.codehaus.groovy:groovy-json:3.0.5")
alias("groovy-nio").to("org.codehaus.groovy:groovy-nio:3.0.5")
alias("commons-lang3").to("org.apache.commons", "commons-lang3").version {
strictly("[3.8, 4.0[")
prefer("3.9")
}
}
}
}

Based on this example, you can find the rules as following:

1
2
3
4
5
6
7
8
9
10
dependencyResolutionManagement {  
versionCatalogs {
create("libs") {
version('version name', 'version code')
alias('alias-sample').to('group', 'artifact').versionRef('version name')
alias('alias.sample').to('group', 'artifact').versionRef('version name')
bundle('bundle-set', ['alias-sample', 'alias.sample'])
}
}
}

It enables you to define your alias and bundle at the top level. You can use them in different modules like, libs.alias.sample or bundle.bundle.set . Version catalogs support autocompletion in the IDE through their type-safe accessors generated by Gradle.

Gradle also supports TOML for the version catalog feature. You would need to create a file called libs.versions.toml in the gradle subdirectory of the root build, then a catalog will be automatically declared with the contents of this file. The TOML file consists of 4 major sections:

  • the [versions] section is used to declare versions that dependencies can reference
  • the [libraries] section is used to declare the aliases to coordinates
  • the [bundles] section is used to declare dependency bundles
  • the [plugins] section is used to declare plugins

Here is an example for this file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[versions]
groovy = "3.0.5"
checkstyle = "8.37"

[libraries]
groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } }

[bundles]
groovy = ["groovy-core", "groovy-json", "groovy-nio"]

[plugins]
jmh = { id = "me.champeau.jmh", version = "0.6.5" }

The usage is very similar when you define those parameters in your gradle files. For example,

1
2
3
4
5
6
7
8
9
10
11
12
// access version section 
buildscript {
dependencies {
classpath "lib.x.y.z:${libs.versions.groovy.get()}"
}
}

// access a single dependency
implementation libs.groovy.core

// access a bundle
implementation libs.bundles.groovy

That’s it. Enjoy the coding.

#Android #Android/Gradle/VersionCatalog