Android Studio Version Catalog in TOML format

The very first thing we will need to do in our newly created Compose project is to identify and set up build dependencies. Those dependencies include libraries and compiler plug-ins, and we will use the (relatively newish) Version Catalog approach to managing dependencies. Version catalogs are especially handy for managing multi-module projects, and since our project is a multi-module project, it is a perfect choice for us. Therefore, it is crucial to understand Version Catalogs first.

What is a Version Catalog?

The official definition of a version catalog in Gradle documentation goes as such:

Version catalog is a list of dependencies, represented as dependency coordinates, that a user can pick from when declaring dependencies in a build script.

Argh. No, let's try it again. Maybe Google does a better job at explaining?

Gradle version catalogs enable you to add and maintain dependencies and plugins in a scalable way. Using Gradle version catalogs makes managing dependencies and plugins easier when you have multiple modules. Instead of hardcoding dependency names and versions in individual build files and updating each entry whenever you need to upgrade a dependency, you can create a central version catalog of dependencies that various modules can reference in a type-safe way with Android Studio assistance.

Whew! Yes they do. We can now summarize the appeal of using Version Catalogs:

  • For each catalog, Gradle generates type-safe accessors so that you can easily add dependencies with autocompletion in the IDE.

  • Each catalog is visible to all projects of a build. It is a central place to declare a version of a dependency and to make sure that a change to that version applies to every subproject.

  • Catalogs can declare dependency bundles, which are "groups of dependencies" that are commonly used together.

  • Catalogs can separate the group and name of a dependency from its actual version and use version references instead, making it possible to share a version declaration between multiple dependencies.

What is TOML?

The Android Studio version catalog is based on TOML format. Let's take a look at TOML and understand how to use it. According to the authors of the TOML language:

TOML - Tom's Obvious Minimal Language TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics. TOML is designed to map unambiguously to a hash table. TOML should be easy to parse into data structures in a wide variety of languages

In other words, it's a very simple language for defining configurations, with the advantage of being easily readable and understandable by humans. There are several building blocks of a TOML document, but for the purposes of Android projects, the three building blocks we are interested in are Key/Value pairs, Tables and Inline Tables

Key/Value Pairs

The primary building block of a TOML document is the key/value pair. Keys are on the left of the equals sign and values are on the right. Whitespace is ignored around key names and values. The key, equals sign, and value must be on the same line (though some values can be broken over multiple lines).


key = "KeyValue"
key.additionalAttribute = "KeyValueAttribute"
    

Tables

Tables (also known as hash tables or dictionaries) are collections of key/value pairs. They are defined by headers, with square brackets on a line by themselves. You can tell headers apart from arrays because arrays are only ever values.


[table-1]
key1 = "some string"
key2 = 123

[table-2]
key1 = "another string"
key2 = 456
    

Inline Tables

Inline tables provide a more compact syntax for expressing tables. They are especially useful for grouped data that can otherwise quickly become verbose. Inline tables are fully defined within curly braces: { and }. Within the braces, zero or more comma-separated key/value pairs may appear. Key/value pairs take the same form as key/value pairs in standard tables. All value types are allowed, including inline tables. Inline tables are intended to appear on a single line. A terminating comma (also called trailing comma) is not permitted after the last key/value pair in an inline table. No newlines are allowed between the curly braces unless they are valid within a value. Even so, it is strongly discouraged to break an inline table onto multiples lines. If you find yourself gripped with this desire, it means you should be using standard tables.


name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }
animal = { type.name = "pug" }
   

Enter The Iguana

You could work with toml configurations in Android Studio for quite some time now but you had to add the toml configuration file yourself. With the release of Android Studio Iguana, a libs.versions.toml file is added by default to any new project you create.

Android Studio TOML file sections

An Android gradle TOML file consists of 4 major sections:

  • the [versions] section is used to declare versions which can be referenced by dependencies

  • 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

When you need to reference a version declared in the [versions] section, you should use the version.ref property:

At the time of this writing, Android Studio generates a libs.versions.toml file with the following content when creating a Compose project:


[versions] #TOML table
agp = "8.3.2" #TOML key/value pair
kotlin = "1.9.0" 
coreKtx = "1.13.1"
junit = "5.0-SNAPSHOT"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.7.0"
activityCompose = "1.9.0"
composeBom = "2024.05.00"

[libraries] #TOML table
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } #TOML inline table with key/values
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }

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

 

Below is an equivalent JSON representation of the same configuration:


{
  "libraries": {
    "androidx-activity-compose": {
      "group": "androidx.activity",
      "name": "activity-compose",
      "version": {
        "ref": "activityCompose"
      }
    },
    "androidx-compose-bom": {
      "group": "androidx.compose",
      "name": "compose-bom",
      "version": {
        "ref": "composeBom"
      }
    },
    "androidx-core-ktx": {
      "group": "androidx.core",
      "name": "core-ktx",
      "version": {
        "ref": "coreKtx"
      }
    },
    "androidx-espresso-core": {
      "group": "androidx.test.espresso",
      "name": "espresso-core",
      "version": {
        "ref": "espressoCore"
      }
    },
    "androidx-junit": {
      "group": "androidx.test.ext",
      "name": "junit",
      "version": {
        "ref": "junitVersion"
      }
    },
    "androidx-lifecycle-runtime-ktx": {
      "group": "androidx.lifecycle",
      "name": "lifecycle-runtime-ktx",
      "version": {
        "ref": "lifecycleRuntimeKtx"
      }
    },
    "androidx-material3": {
      "group": "androidx.compose.material3",
      "name": "material3"
    },
    "androidx-ui": {
      "group": "androidx.compose.ui",
      "name": "ui"
    },
    "androidx-ui-graphics": {
      "group": "androidx.compose.ui",
      "name": "ui-graphics"
    },
    "androidx-ui-test-junit4": {
      "group": "androidx.compose.ui",
      "name": "ui-test-junit4"
    },
    "androidx-ui-test-manifest": {
      "group": "androidx.compose.ui",
      "name": "ui-test-manifest"
    },
    "androidx-ui-tooling": {
      "group": "androidx.compose.ui",
      "name": "ui-tooling"
    },
    "androidx-ui-tooling-preview": {
      "group": "androidx.compose.ui",
      "name": "ui-tooling-preview"
    },
    "junit": {
      "group": "junit",
      "name": "junit",
      "version": {
        "ref": "junit"
      }
    }
  },
  "plugins": {
    "androidApplication": {
      "id": "com.android.application",
      "version": {
        "ref": "agp"
      }
    },
    "jetbrainsKotlinAndroid": {
      "id": "org.jetbrains.kotlin.android",
      "version": {
        "ref": "kotlin"
      }
    }
  },
  "versions": {
    "activityCompose": "1.9.0",
    "agp": "8.3.2",
    "composeBom": "2024.05.00",
    "coreKtx": "1.13.1",
    "espressoCore": "3.5.1",
    "junit": "5.0-SNAPSHOT",
    "junitVersion": "1.1.5",
    "kotlin": "1.9.0",
    "lifecycleRuntimeKtx": "2.7.0"
  }
}

         

Alias, Libs and Platform

alias adds a plugin dependency using a notation coming from a version catalog. The notation is of type Provider<PluginDependency>


    open fun alias(
        notation: Provider<PluginDependency>
    ): PluginDependencySpec

Note that you must use alias when working with toml version catalogs. Using the id function will not work.


    # with Version Catalogs
    plugins {
        alias(libs.plugins.androidApplication)
        alias(libs.plugins.jetbrainsKotlinAndroid)
    }

    # equivalent without Version Catalog:
    plugins {
        id 'com.android.application' version '7.1.3' apply false
        id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
    }

     # using id with libs does not work. id expects a string as an argument
    plugins {
        id(libs.plugins.androidApplication) #build error
        id(libs.plugins.jetbrainsKotlinAndroid) #build error
    }
    # the above results in the following error:
        Type mismatch.
            Required:
            String
            Found:
            Provider<PluginDependency!>

The Java Platform plugin brings the ability to declare platforms for the Java ecosystem. A platform can be used for different purposes: a description of modules which are published together (and for example, share the same version) a set of recommended versions for heterogeneous libraries. A typical example includes the Spring Boot BOM sharing a set of dependency versions between subprojects A platform is a special kind of software component which doesn't contain any sources: it is only used to reference other libraries, so that they play well together during dependency resolution.


    dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(platform(libs.androidx.compose.bom))
   
}

   
    default Provider platform(Provider dependencyProvider) {
        return variantOf(dependencyProvider, ExternalModuleDependencyVariantSpec::platform);
    }  

On to Step 2: Setting Up Navigation