Gradle Plugin Tutorial for Android: Getting Started

Learn how to create a Gradle plugin within your existing Android app, or as a standalone project that you can publish and use in any Android project. By Bhavesh Misri.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Adding More Tasks

You can create more tasks in the apply function the same way you created renameApk, but it’s better to separate tasks into different classes to keep things clean and reusable.

Create a folder inside the java folder and name it tasks. Inside the tasks folder, create a new Kotlin class named ManageApk.

In the ManageApk class, add the following code:

package tasks

import AppDetail.newApkName
import AppDetail.previousName
import AppDetail.previousPath
import AppDetail.targetPath
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import java.io.File

open class ManageApk: DefaultTask() {
  @TaskAction
  fun renameApk() {
    val newPath = File("${project.buildDir.absoluteFile}/$previousPath/$previousName")
    if (newPath.exists()) {
      val newApkName = "${project.buildDir.absoluteFile}/$previousPath/$newApkName"
      newPath.renameTo(File(newApkName))
    } else {
      println("Path not exist!")
    }
    moveFile()
  }

  private fun moveFile() {
    File("${project.buildDir.absoluteFile}/$previousPath/$newApkName").let { sourceFile ->
      try {
        sourceFile.copyTo(File("$targetPath/$newApkName"))
      } catch (e: Exception) {
        e.printStackTrace()
        val folder = File(targetPath)
        folder.mkdir()
      } finally {
        sourceFile.delete()
      }
    }
  }
}

By extending the class with DefaultTask, you have the ability to define your own tasks. To create your own task, you need to annotate the function with @TaskAction and then write your logic inside it. The moveFile function will move the APK created to your desired location (make sure to change the location inside Constant.kt). Your task class is ready now.

To use the task in your plugin, open your BuildManager plugin class, and inside apply(), replace the existing code with:

project.tasks.register<ManageApk>("renameApk") {
  dependsOn("build")
}

Add the following imports as well:

import org.gradle.kotlin.dsl.register
import tasks.ManageApk

You’ve now registered the task you created in your ManageApk class in your plugin class. The task will be executed when renameApk is called, the latter of which depends on build task.

Now, sync and run ./gradlew clean to clean the project. In your terminal, run ./gradlew -q renameApk, and your APK will be renamed to whatever name you gave the app in Constant.kt. Its suffix should be today’s date and the version code you provided. It’ll also move the APK to your desired location.

Now you’ll make the final changes. Create ManageDependency.kt inside the tasks directory and add the following code:

package tasks

import AppDetail.dependencyFileName
import AppDetail.targetPath
import getDate
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import java.io.File

open class ManageDependency : DefaultTask() {
  @TaskAction
  fun saveDependencies() {
    var file = File("$targetPath/$dependencyFileName")
    if (!file.exists()) {
      println("Path does not exist. Creating folder...")

      val folder = File(targetPath)
      if (!folder.exists()) {
        folder.mkdir()
      }
        
      file = File(targetPath, dependencyFileName)
      file.createNewFile()
    }
    writeDependencies(file)
  }

  private fun writeDependencies(file: File) {
    file.appendText(getDate(true), Charsets.UTF_8)
    file.appendText(
        "\n${AppDetail.appName} - ${AppDetail.versionName}(${AppDetail.versionCode})\n",
        Charsets.UTF_8
    )
    file.appendText("====Dependencies====\n", Charsets.UTF_8)

    project.configurations.asMap.forEach {
      if (it.key.contains("implementation")) {
        it.value.dependencies.forEach { dependency ->
          file.appendText(
              "${dependency.group}:${dependency.name}:${dependency.version}\n",
              Charsets.UTF_8
          )
        }
      }
    }

    file.appendText("====================", Charsets.UTF_8)
    file.appendText("\n\n\n", Charsets.UTF_8)
  }
}

These tasks will create a text file and add all the dependencies of your module in the file every time you create a build. But for that, you need to add these tasks in your plugin class. Open your BuildManager plugin class and add the following lines inside the apply function:

project.tasks.register<ManageDependency>("saveDependencies") {
  dependsOn("renameApk")
}

project.task("createBuild").dependsOn("saveDependencies")

Add the import statement as follows:

import tasks.ManageDependency

Your complete BuildManager class will look like this:

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.invoke
import org.gradle.kotlin.dsl.register
import task.ManageApk
import task.ManageDependency

class BuildManager : Plugin<Project> {
  override fun apply(project: Project) {
    project.tasks.register<ManageApk>("renameApk") {
      dependsOn("build")
    }

    project.tasks.register<ManageDependency>("saveDependencies") {
      dependsOn("renameApk")
    }

    project.task("createBuild").dependsOn("saveDependencies")
  }
}

Now you can test the plugin you created. Firstly, delete the newOutput folder. Run ./gradlew clean to delete the build directory, and then run ./gradlew -q createBuild in the terminal to test the plugin.

That’s it for creating a Gradle plugin in buildSrc!! Finally, you’ll learn how to create the plugin as a standalone project in the next section.

Creating a Standalone Plugin Project

For the standalone project, you’ll keep things simpler and create a plugin that only creates a text file, and then add the dependencies of the project in that file. Creating a standalone project will give you the capability to publish and share it with others. The recommended and easiest way is to use Java Gradle Plugin. This will automatically add the gradleApi() dependency, generate the required plugin descriptors in the resulting JAR file and configure the Plugin Marker Artifact to be used when publishing.

To start, extract and open the starter project with the name ProjectTrackerPlugin in IntelliJ.

In build.gradle, add java-gradle-plugin and maven inside the plugins task so that it looks like this:

plugins {
  id 'java'
  id 'java-gradle-plugin'
  id 'maven'
  id 'org.jetbrains.kotlin.jvm' version '1.4.32'
}

These two plugins will help you create the plugin and publish it. After you’ve made the changes, load them by pressing Command-Shift-I if you’re on macOS or Control-Shift-O if you’re on PC. Next, create a package inside the src/main/kotlin directory and name it com.raywenderlich.plugin. Inside the directory, first create an open Kotlin class, SaveDependencyTask, and extend it with DefaultTask().

Create a companion object and add the following constants, which will help determine where to save the text file:

companion object {
  private const val targetPath = "newOutput"
  private const val dependencyFileName = "Dependencies.txt"
}

Create a variable configuration which will be of type Collection<String>, and annotate it with @Input:

@Input
var configuration: Collection<String> = mutableListOf()

Doing the above allows you to specify that there’s some input value for this task. It’ll be used to pass the list of configurations from the module using this plugin so that it can have access to all its dependencies.

Next, create a function, checkDependency, and annotate it with @Input. Inside the function, add the following lines:

var file = File("$targetPath/$dependencyFileName")
if (!file.exists()) {
  println("Path does not exist. Creating folder...")

  val folder = File(targetPath)
  if (!folder.exists()) {
    folder.mkdir()
  }
  
  file = File(targetPath, dependencyFileName)
  file.createNewFile()
}
writeDependencies(file)

The code above is pretty much the same code you wrote in your buildSrc plugin. You’re checking if the path exists or not, and if not, you’re creating the required directories and files.

Finally, add the remaining functions, which will actually perform the writing in the text file:

private fun writeDependencies(file: File) {
  file.appendText(getDate(), Charsets.UTF_8)
  file.appendText("\n====Dependencies====\n", Charsets.UTF_8)
  configuration.forEach {
    project.configurations.getByName(it).dependencies.forEach { dependency ->
      file.appendText(
          "${dependency.group}:${dependency.name}:${dependency.version}\n",
          Charsets.UTF_8
      )
    }
  }
  file.appendText("====================", Charsets.UTF_8)
  file.appendText("\n\n\n", Charsets.UTF_8)
}

private fun getDate(): String {
  val current = LocalDateTime.now()
  val formatter = DateTimeFormatter.ofPattern("dd MMM, yyy")
  return current.format(formatter)
}

With this, your task class is ready. Now, create a Kotlin class in the same directory and name it SaveDependency and implement Plugin. In the apply function, register the task you created so that the class looks like this:

package com.raywenderlich.plugin

import org.gradle.api.Plugin
import org.gradle.api.Project

class SaveDependency: Plugin<Project> {
  override fun apply(project: Project) {
    project.tasks.register("writeModuleDependencies", SaveDependencyTask::class.java)
  }
}

Voila! You created your own Gradle plugin in a standalone project.

Next, you’ll look at how you can use this plugin in your Android Studio projects.