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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Gradle Plugin Tutorial for Android: Getting Started
25 mins
Creating a Plugin Using the buildSrc Directory
Now you’ll create a plugin in a different way. First, create a directory, buildSrc, at the project’s root. This is where you’ll add your build logic, and as it supports Kotlin DSL, you can write the plugin in Kotlin.
Inside the new directory, create a file, build.gradle.kts. Inside the file, add the following code:
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
So, what’s happening here? Well, first of all buildSrc is a directory that Gradle looks at while compiling. If it finds any custom build code, it adds that to Gradle’s classpath.
In this file, you’re applying the kotlin-dsl plugin, but if you try to sync the project only by adding this, it won’t work. You need to add mavenCentral()
in repositories
as well, because the plugin is hosted there. After you’ve added these lines, sync the project and you’ll see a few more directories in the buildSrc folder. Your project structure will look something like this:
This means your build.gradle.kts file was successfully added to Gradle’s classpath. Next, right-click on the buildSrc folder and select New ▸ Directory, and then select src/main/java.
Here, you can start writing your plugins in Kotlin and all the modules can access the plugin. Open the java folder and create a Kotlin class, BuildManager, that implements Plugin:
import org.gradle.api.Plugin
import org.gradle.api.Project
class BuildManager : Plugin<Project> {
override fun apply(project: Project) {
}
}
You can create tasks within this apply function in a way similar to how you did in your module level build.gradle file earlier. For this, you’ll create a task which will do a bit more than printing a sentence.
Inside apply, include the following code:
project.task("renameApk") {
doLast {
val apkPath = "/outputs/apk/release/app-release-unsigned.apk"
val renamedApkPath = "/outputs/apk/release/RenamedAPK.apk"
val previousApkPath = "${project.buildDir.absoluteFile}$apkPath"
val newPath = File(previousApkPath)
if (newPath.exists()) {
val newApkName = "${project.buildDir.absoluteFile}$renamedApkPath"
newPath.renameTo(File(newApkName))
} else {
println("Path does not exist!")
}
}
}.dependsOn("build")
Add an import for the File class at the top of the file:
import java.io.File
You’re creating a task called renameApk, and in that class, you’re finding where your APK is located and then renaming it. But what if the APK hasn’t yet been created? Or, what if you removed the file for some reason? Well, that’s where the last line, .dependsOn("build")
, comes to the rescue. This function will create a dependency on the build task that will create the APK.
But if you try to execute the task from the terminal, it’ll fail and give you an error saying BUILD FAILED
. This is because you havn’t yet applied the plugin to your project. To do that, go into your build.gradle.kts file and add the following:
gradlePlugin {
plugins {
create("BuildManager") {
id = "com.raywenderlich.plugin"
implementationClass = "BuildManager"
version = "1.0.0"
}
}
}
In the code above, you’re registering your plugin by using one of the extension functions, gradlePlugin. By using the create function, you can give a name, ID, version, and reference of the plugin class you created.
Now it’s time to add the registered plugin to your module. Jump into your module-level build.gradle file and add it as the last item in the plugins task:
plugins {
...
id 'com.raywenderlich.plugin'
}
You’re ready to go! Sync the project, run ./gradlew clean
to clean up the build directory and then execute your task with ./gradlew -q renameApk
. Go to the /app/build/outputs/apk/release folder and find your renamed APK.
Dependency Management
Before adding more features to your plugin, you’ll clean up the project a bit. Create a new Kotlin file in your java folder and name it Constant. Add the following code in the file:
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
object Dependencies {
const val kotlinCore = "androidx.core:core-ktx:${Versions.kotlinCoreVersion}"
const val appCompat = "androidx.appcompat:appcompat:${Versions.appCompatVersion}"
const val material = "com.google.android.material:material:${Versions.materialVersion}"
const val constraint = "androidx.constraintlayout:constraintlayout:${Versions.constraintVersion}"
const val jUnit = "junit:junit:${Versions.jUnitVersion}"
const val jUnitExt = "androidx.test.ext:junit:${Versions.jUnitExtVersion}"
const val espresso = "androidx.test.espresso:espresso-core:${Versions.espressoVersion}"
const val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlinStdLibVersion}"
const val kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlinStdLibVersion}"
const val buildToolGradle = "com.android.tools.build:gradle:${Versions.buildToolGradleVersion}"
}
object Versions {
const val compileSdkVersion = 30
const val buildToolsVersion = "30.0.3"
const val minSdkVersion = 21
const val targetSdkVersion = 30
const val kotlinStdLibVersion = "1.4.32"
const val buildToolGradleVersion = "4.1.3"
const val kotlinCoreVersion = "1.3.2"
const val appCompatVersion = "1.2.0"
const val materialVersion = "1.3.0"
const val constraintVersion = "2.0.4"
const val jUnitVersion = "4.13.2"
const val jUnitExtVersion = "1.1.2"
const val espressoVersion = "3.3.0"
}
object AppDetail {
const val applicationId = "com.raywenderlich.projecttracker"
const val appName = "ProjectTracker"
const val versionCode = 1
const val versionName = "1.0.0"
// Change these values as needed
const val previousPath = "/outputs/apk/release"
const val targetPath = "newOutput"
const val previousName = "app-release-unsigned.apk"
val newApkName = "$appName-${getDate(false)}($versionCode).apk"
const val dependencyFileName = "Dependencies.txt"
}
fun getDate(forTxtFile: Boolean): String {
val current = LocalDateTime.now()
val formatter = if (forTxtFile) {
DateTimeFormatter.ofPattern("dd MMM, yyy")
} else {
DateTimeFormatter.ofPattern("yyyyMMdd")
}
return current.format(formatter)
}
The Dependencies, Version and AppDetail classes will be used to manage dependencies, and you’ll also be using the AppDetail class and getDate() function to add more functionality to your plugin. All these do at the moment is to hold the build information in classes. Next, you can jump into the Gradle files of your project and update your dependencies to make it look cleaner and to manage them better.
After the changes, your module-level build.gradle file will look like this:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.raywenderlich.plugin'
}
android {
compileSdkVersion Versions.compileSdkVersion
buildToolsVersion Versions.buildToolsVersion
defaultConfig {
applicationId AppDetail.applicationId
minSdkVersion Versions.minSdkVersion
targetSdkVersion Versions.targetSdkVersion
versionCode AppDetail.versionCode
versionName AppDetail.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation Dependencies.kotlinStdLib
implementation Dependencies.kotlinCore
implementation Dependencies.appCompat
implementation Dependencies.material
implementation Dependencies.constraint
testImplementation Dependencies.jUnit
androidTestImplementation Dependencies.jUnitExt
androidTestImplementation Dependencies.espresso
}
And your top-level or project-level build.gradle file will look like this:
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath Dependencies.buildToolGradle
classpath Dependencies.kotlinGradlePlugin
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
With that, it’s time to add more tasks to the plugin class.