DEV Community

Sridhar Subramani
Sridhar Subramani

Posted on

Simplify Your Android Builds: A Guide to Convention Plugins

Simplify Your Android Builds: A Guide to Convention Plugins

Learn How to Create and Use Custom Gradle Plugins

Photo by [Birger Strahl](https://unsplash.com/@bist31?utm_source=medium&utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&utm_medium=referral)Photo by Birger Strahl on Unsplash

What’s a Convention Plugin

A convention plugin is a structured approach to organizing Gradle files, which can be used as a plugin within the Gradle
module.

Benefits

  • Reusable: Plugins can be reused across multiple Gradle modules, enhancing consistency and efficiency.

  • Easy to Maintain: Complex build logic can be developed by extending or composing other build logic and can be encapsulated into dedicated plugins (such as ArtifactDeploymentPlugin, AndroidLibraryPlugin, and AndroidComposePlugin, etc.) for ease of use.

  • Easy to Consume: By applying plugins, users can easily add new features to their build process without having to write complex code from scratch again.

  • Testable Build Logic: The build logic can be tested using TestKit to verify its behavior. (Yet to try this)

How to Add This Convention Plugin

Step 1: Create the build-logic Folder

  • Create a separate build-logic folder to organize your convention plugin code and separate it from the rest of your project’s code.

  • At the root level, create a settings.gradle.kts file with the following content:

     dependencyResolutionManagement {

          repositories {
              google()
              mavenCentral()
          }

          versionCatalogs {
              create("libs") {
                  from(files("../gradle/libs.versions.toml")) // Create this file if not present.
              }
          }
      }

      rootProject.name = "build-logic"
      // include(":convention") // Enable this line only after step 2
Enter fullscreen mode Exit fullscreen mode

Inside the project’s settings.gradle.kts, add includeBuild(“build-logic”) within the pluginManagement block.

      pluginManagement {

          includeBuild("build-logic") // Include the build-logic

          repositories {
              // ...
              gradlePluginPortal()
          }
      }

      // ...
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Build Convention Module

  • Add a new Java/Kotlin module under build-logic and name it as convention. In this example, the package name is com.droidstarter.convention.

  • Ensure that any entry like include(“:build-logic:convention”) in the root settings.gradle.kts file is removed (Studio will add this entry automatically when a new module is created).

  • Replace the script in the convention module’s build.gradle.kts with:

    plugins {
        `kotlin-dsl`
    }

    group = "com.droidstarter.buildlogic" // Package name for the our plugins

    java {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    dependencies {
        compileOnly(libs.android.gradlePlugin)
        compileOnly(libs.kotlin.gradlePlugin)
    }

    gradlePlugin {
        plugins {
            // Will add in next step
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • Add the following dependencies to libs.versions.toml:
    [versions]
    androidGradlePlugin = "8.0.2" # use latest version
    kotlin = "1.9.22" # use latest version

    [libraries]
    android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
    kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
Enter fullscreen mode Exit fullscreen mode
  • Add include(“:convention”) to the settings.gradle.kts file in the build-logic module, then perform a Gradle sync.

Step 3: Create a Custom Gradle Convention Plugin

  • First, set up a basic convention plugin named AndroidApplicationComposeConventionPlugin. This plugin will centralize
    all the build configurations needed for setting up an Android project with Jetpack Compose.

  • ImplementPlugin<Project> to define and apply custom configurations.

    class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
        override fun apply(target: Project) {
            println("*** AndroidApplicationComposeConventionPlugin invoked ***")
            // Additional configuration here...
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • Register the plugin inside the build-logic’s build.gradle.kts, making it accessible for use in other modules:
    plugins {
        `kotlin-dsl`
    }

    // ...

    gradlePlugin {
        plugins {
            create("androidApplicationCompose") {
                id = "com.droidstarter.convention.application.compose" // This is the id we used to resolve our plugin.
                implementationClass = "com.droidstarter.convention.AndroidApplicationComposeConventionPlugin"
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • Apply the newly created plugin to your app module or any Android application module.
    plugins {
        id("com.droidstarter.convention.application.compose")
    }
Enter fullscreen mode Exit fullscreen mode
  • Perform a Gradle sync, and you should now see the following logs in the build window:

**** AndroidApplicationComposeConventionPlugin invoked ****

Step 4: Configure the Plugin for Android App with Jetpack Compose Build Logic

  • Apply the Android and Kotlin plugins within your convention plugin so that when it is applied, those plugins are automatically included. This allows you to remove them from the app module’s build.gradle.kts.
    class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
        override fun apply(target: Project) {
            println("*** AndroidApplicationComposeConventionPlugin invoked ***")
            with(target) {
                with(pluginManager) {
                    apply("com.android.application") // Include android application plugin
                    apply("org.jetbrains.kotlin.android") // Ensure project build.gradle declared this plugin
                }
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • Configure common settings like compileSdk, minSdk, targetSdk, sourceCompatibility, targetCompatibility, and kotlinJvmTarget.

  • Set up Jetpack Compose configuration, including enabling compose features and adding necessary dependencies.

class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
        override fun apply(target: Project) {
            println("*** AndroidApplicationComposeConventionPlugin invoked ***")
            with(target) {
                with(pluginManager) {
                    apply("com.android.application")
                    apply("org.jetbrains.kotlin.android") // Ensure project build.gradle declared this plugin
                }

                extensions.configure<ApplicationExtension> {
                    configureKotlinAndroid(this)
                    defaultConfig.targetSdk = 34
                }
            }
        }
    }


    internal fun Project.configureKotlinAndroid(commonExtension: CommonExtension<*, *, *, *>) {
        val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")

        commonExtension.apply {
            compileSdk = 34

            defaultConfig {
                minSdk = 29
            }

            compileOptions {
                sourceCompatibility = JavaVersion.VERSION_11
                targetCompatibility = JavaVersion.VERSION_11
            }

            buildFeatures {
                compose = true
            }

            composeOptions {
                // Add androidxComposeCompiler in toml
                kotlinCompilerExtensionVersion = libs.findVersion("androidxComposeCompiler").get().toString()
            }

            dependencies {
                // Add androidx-compose-bom in toml
                val bom = libs.findLibrary("androidx-compose-bom").get()
                add("implementation", platform(bom))
                add("androidTestImplementation", platform(bom))
            }
        }

        tasks.withType<KotlinCompile>().configureEach {
            kotlinOptions {
                jvmTarget = JavaVersion.VERSION_11.toString()
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • Add the following dependencies to libs.versions.toml:
    [versions]
    androidxComposeCompiler = "1.5.10" # use latest version
    androidxComposeBom = "2024.02.01" # use latest version

    [libraries]
    androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
Enter fullscreen mode Exit fullscreen mode

Conclusion

  • If the steps outlined above feel overwhelming, you can simplify the process by cloning the Android template repository available at my GitHub.

  • This repository already contains the essential build configurations and boilerplate setups, including Hilt, JaCoCo test reporting, and plugins for Android Library and Jetpack Compose, among other features.


Top comments (0)