· 7 min read Posted by Kevin Schildhorn

Gradle Cheat Sheet for Android and KMP Projects - Plugins

Gradle can be confusing and difficult to follow. When you create a new Android project they generate some Gradle files and send you off. Often, developers end up copying and pasting code from GitHub or Stack Overflow, not really understanding what’s going on.

This series will provide some definition of commonly seen code in Gradle builds, and act as a cheat sheet you can reference in the future. In addition to this post, I’ve also created a GitHub Gist that should cover a typical Gradle project setup, and contain a markdown cheatsheet. If you want to follow along with an existing project, I’d recommend either this multiplatform compose template or KaMP Kit, which is a great starting point for a KMM project.

In this article we’ll explore how gradle works, dive deeper into Gradle Plugins, and leave you with some handy Flashcards for reference.

What actually happens in a Gradle build

Before we start going over plugins let’s go over what happens in a build:

  1. The Gradle wrapper is downloaded based on the gradle-wrapper.properties
  2. The build detects the settings.gradle file and evaluates to determine which projects participate.
  3. Project instances are created for every project and their build.gradle scripts are all evaluated.
  4. A task graph for requested tasks is created.
  5. Each of the selected tasks are scheduled and executed, in the order of their dependencies.

The main thing to note here is the order the build runs in. It first checks the settings.gradle then goes through each submodule defined in the settings, and runs their build.gradle files. Then as mentioned it executes tasks in order of dependencies. The actual order may be complex, but as a simplification in a KMP example we can say this:

  • Settings.gradle runs build.gradle of each submodule
    • settings.gradle or build.gradle resolves plugins
    • build.gradle checks for blocks to run (such as android and kotlin) and runs them
    • Those blocks resolve dependencies

The order I like to think of this is:

Settings.gradle -> build.gradle -> plugins -> blocks -> dependencies

So with that in mind, we’ll first go over plugins, then common kotlin and android blocks, then dependencies, and finally wrap back to some notes about the settings.gradle. Gradle Source

Note: Gradle script files can come in two different extensions based on the chosen DSL: .gradle for Groovy DSL and .gradle.kts  for Kotlin DSL. This blog post refers to these two extensions interchangeable, as it discusses the concepts of Gradle rather than just the syntax.

Note: In Gradle there are many ways to do the same thing. This post won’t necessarily go over best practices for location, but will give some suggestions on how to structure definitions in a multi-module project.

Note: This is not only a cheatsheet for you, but also for me. I am still learning, so if I have not covered something, or covered something incorrectly let me know.

Definitions

Before we start here are some terms I use during the post that might not be clear:

  • Level: In this post when I say “level” I mean the module level. For example the root or top level would imply the root directories build.gradle and module.
  • DSL: Domain Specific Language. Examples: Kotlin DSL, Plugins DSL, Gradle DSL

Plugins Overview

Plugins DSL

// yourModule/build.gradle.kts
plugins {
    kotlin("multiplatform")     // This is where you add your plugin
}
// settings.gradle.kts (root)
pluginManagement {              // Where you configure the plugins used in this project.
    repositories {              // Listing where you want to pull the plugins from
        google()
        mavenCentral()          // The Central Maven Repository (more about maven later on)
        gradlePluginPortal()    // Default Gradle plugin portal (https://plugins.gradle.org/)
    }
    plugins {                   // Listing the plugins you will use
        kotlin("multiplatform")
            .version("1.8.10")    // Plugins must contain a version in their definition at some level
            .apply(false)       // You can choose to not apply plugins, in case you don't want to use the plugin at a certain level
    }
}

Legacy Plugin Application

buildscript {
  repositories { // Listing where you want to pull the plugins from
    maven { url = uri("https://plugins.gradle.org/m2/") }
  }
  dependencies {
    classpath("do.re.mi.exam:ple:1.8.20") // This a dependency for the plugins you want to add
  }
}
apply(plugin = "org.jetbrains.kotlin.multiplatform") // Here is where you define a plugin

Gradle Docs

What are plugins

Plugins add functionality to your Gradle build. They add new tasks, domain objects, and features to the project. More info here

Example: Adding the kotlin("multiplatform") allows you to add the kotlin block containing sourcesets and targets and adds tasks such as build iosArm64Binaries

How are plugins added?

Adding plugin to module

Let’s view this from a top down approach, starting at a modules build.gradle. First, We add the plugin.

// build.gradle
plugins {
    id("com.android.library")
    kotlin("multiplatform")
        .version("1.8.10")  // Optional\*
        .apply(false)     // Optional
}

Plugins are added in the plugins block, which can either be added to a build.gradle or to a pluginManagement block in the settings.gradle (we’ll get to that). Submodules inherit plugins, so if you want to share plugins among submodules they can be defined to the root module.

There are two ways to include a plugin:

  • id("id"): With id you are importing a plugin, by passing in the group ID and name of the plugin
  • kotlin("id"): Kotlin is a nice shorthand, that simply means we’re using the “org.jetbrains.kotlin” group ID. This is the same as calling id("org.jetbrains.kotlin.id")

Version: You must add a version to the plugin in your project. If you share the plugin with submodules you can define the version in the root module, otherwise you set the version in the module it’s used. Versions are inherited by submodules, but you can also override versions in submodules if needed.

Apply: You can choose to apply or not apply plugins using apply. This is good in cases where you want to define the plugin globally in the root project, but don’t want to actually use it in the root module. Submodules that declare this plugin will automatically have it applied.

KMP Note: Plugins defined in a module apply to all sourcesets. For example if you include the id("org.jetbrains.kotlin.id") plugin, then it will try to apply that plugin to your iosMain sourceset and cause an error.

Managing plugin

Second, we define where it’s coming from.

// settings.gradle
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

You need to define where the plugins come from, otherwise Gradle has no idea what you’re talking about. By default Gradle checks for plugins at the gradlePluginPortal. If the plugin you’re using is in another repo you can define it in the pluginMagement block. The pluginManagement block is where you set the repositories your plugins come from. Additionally you can set plugins here as well, using the same syntax as mentioned above. We’ll go over maven in a future post but for now know that mavenCentral is a popular repository used for plugins and dependencies.

Legacy

buildscript {
  repositories { 
    mavenCentral()
  }
  dependencies {
    classpath(
        "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20"
    )
  }
}
apply(plugin = "org.jetbrains.kotlin.multiplatform")

Legacy is a little different. Instead of pluginManagement we use buildScript. Repositories are the same, but note there’s a Repositories block in this version. This is where you define classpaths, which are dependencies for plugins.

Personal recommendation

There’s a lot of different ways of defining plugins, but as of May, 2023, here is what I would recommend:

  • Follow the android guidelines
  • Define all plugin information settings.gradle using pluginManagement, with versions, but set apply to false.
  • Then include plugins in submodules, but without the version

Flashcards

Here are some quick references to code surrounding Gradle plugins

Blocks

Full Coverage

  • plugins: Where you define the plugins you want to use (More Info)
  • repositories: A block that tells Gradle where to look for items (More Info)
  • pluginManagement– Where you define the project’s plugins, contains plugins and repositories blocks. (More Info)
  • BuildScript: (Legacy for plugin) Defines repositories and dependencies for adding plugins. Can also be used to add variables such as versions that are used across the project (More Info)

Plugins

  • id("id"): Add a plugin with a group ID and a name (ex: “com.android.library”) (More Info)
  • kotlin("id"): Kotlin simply means we’re using the “org.jetbrains.kotlin” group ID
  • version("version"): Setting the version of the plugin, required at some module level (More Info)
  • apply(boolean): Whether to apply the plugin or not. Useful if you want to define the plugin in the root module, but only use it in other certain submodules (More Info)

Legacy

  • classpath: A dependency for the plugins you want to add, classpath is used in the buildscript block
  • apply("id"): The legacy way to add a plugin

Repositories