Simplify Your Android Builds: A Guide to Convention Plugins
Learn How to Create and Use Custom Gradle Plugins
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-logicfolder 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.ktsfile 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
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()
          }
      }
      // ...
Step 2: Create the Build Convention Module
- Add a new Java/Kotlin module under - build-logicand 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 - conventionmodule’s- build.gradle.ktswith:
 
    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
        }
    }
- 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" }
- Add include(“:convention”)to thesettings.gradle.ktsfile in thebuild-logicmodule, 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.
- Implement - Plugin<Project>to define and apply custom configurations.
 
    class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
        override fun apply(target: Project) {
            println("*** AndroidApplicationComposeConventionPlugin invoked ***")
            // Additional configuration here...
        }
    }
- 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"
            }
        }
    }
- Apply the newly created plugin to your app module or any Android application module.
    plugins {
        id("com.droidstarter.convention.application.compose")
    }
- 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
                }
            }
        }
    }
- 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()
            }
        }
    }
- 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" }
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)