One of the major challenge for the android developers to understand the how the Android Gradle Plugin

(AGP) works. As android devs we mostly use the ready-to-develop gradle plugin settings or just add thedependencyResolutionManagement settings with respect toJCenter or Maven. So, why bother to write your own TransformAction for Android Gradle Plugin (AGP)?

  1. What if you want to achieve the same features among multiple modules or projects?
  2. Sometimes you want to automate some boring tasks (for example, measuring the execution time of annotated methods or logging method calls with their arguments) or modify third-party dependencies in your own favor. The new Android Gradle plugin (AGP) has been providing Transform API for such cases, but it’s proved to be inefficient and may be the cause of slow builds. The new API TransformAction is much more efficient and easier to consume, so one can directly start modifying bytecode with almost zero-setup. In july 2021, the Android Gradle Plugin was updated to version 7.0. Everyone was surprised to find that the Transform class marked as depreceated, and no direct replacement class was provided in the comments. So, an article, I found on the internet mentioned an interface: TransformAction is the replacement of Transform.
  3. Is it really?
  4. What TransformAction exactly is?
  5. Can it completely replace Transform?
  6. How it compares to old one?

I think now you guys have some kind of understanding that what this article is all about. We gonna see the explore the TransformAction and show how this transformation can be useful for automating tedious or even impossible from the source code prespective.

android gradle 2

Brief Intro of TransformAction in Gradle (AGP)

The best way to learn about a module is to look at the official documentation. I know, it is too long and boring 😂 you can read my summary for it. Simply put it as TransformAction is the Gradle provided product transition between properties and switch dependencies from one state to another.

In the process of developing various application our project may have multiple variant, i.e. a dependency may have two variants classes or jar. Now when the Gralde configuration is parsing out the dependency which doesn’t have the variant of the requested property, then the parsing configuration fails.

So, we can download (decompressed) Jar with TransformAction and convert java-api jar to a java-api classes variant. This transformation can transform dependent products, so it is called product transformation

Process to Write the Custom TransformAction

The process to write custom TransformAction for Android Gralde is quite interesting.

  • Register the corresponding DependencyHandler Transform.
  • Create custom configuration
  • Dependency conversion is performed when the corresponding configuration is received.
  • At compile time, take out the path of these artifcats.

Writing Custom TransformAction For Android Gradle With Kotlin DSL

I know you guys must be thinking about why the hack now the Kotlin DSL? Actually Gradle provide an alternative syntax to the traditional Groovy DSL with the enhanced experience supported in Android Studio. Mostly android devs are already using Kotlin Programming Language for development of mobile application.

I’m not gonna go in the detail of how to write Kotlin DSL. For more info about DSL’s check out this link.

For the example we gonna implement a Unzip TransformAction which transform a Jar file into classes directory by unzipping it. The Unzip transform does not require any parameters.

First create new buildSrc directory in the same-level of app directory and create new Unzip name class or maybe any other name which you prefer.

android gradle 1

Second copy the following the code and paste inside the Unzip class.

abstract class Unzip : TransformAction<TransformParameters.None> {  // 1
    @get:InputArtifact
    abstract val inputArtifact: Provider<FileSystemLocation>  //2
    override
    fun transform(outputs: TransformOutputs) {
        val input = inputArtifact.get().asFile
        val unzipDir = outputs.dir(input.name) // 3
        unzipTo(input, unzipDir) // 4
    }
    private fun unzipTo(zipFile: File, unzipDir: File) {
    }
}

Here you:

  1. Using the TransformParameters.None when there is not parameters to use.
  2. Inject the input artifact.
  3. Request an output location for the unzipped files.
  4. Do unzip the jar and transform.

Regiserting TransformAction Transforms

It can be seen that the input file content and the output address can be obtained through the framework. So where the input come from? Do all dependent files need to be processed as input? Obviously not, as mentioned earlier, we need to register the artifact transform actions, providing parameters if necessary, so that they can be selected when resolving dependencies.

In order to register an artifact transform, you must use registerTransform() within the dependencies{} block. We can do so by registering an artifact transform action of type Unzip, as show below in build.gradle.kts file.

val artifactType = Attribute.of(“artifactType”, String::class.java)
dependencies {
    registerTransform(Unzip::class) {
        from.attribute(artifactType, “jar”)
        to.attribute(artifactType, “my-custom-type”)
    }
}

When we receive an artifact of jar this class (artifact), it is called Unzip, and it is converted to my-custom-type. The my-custom-type is just a state flag, which is convenient for us to process it later.

Another example is that you want to unzip JARs by only keeping some class files from them by passing the custom parameters. Note the use of the parameters{} block to provide the classes to keep in the unzip transformer.

val artifactType = Attribute.of(“artifactType”, String::class.java)
val keepPatterns = mapOf(
    “custom-type” to setOf(
        “com.google.common.base.Optional”,
        “com.google.common.base.AbstractIterator”
    )
)
dependencies {
    registerTransform(Unzip::class) {
        from.attribute(artifactType, “jar”)
        to.attribute(artifactType, “my-custom-type”)
        parameters {
            keepClassesByArtifact = keepPatterns
        }
    }
}

Now we need to have an updated Unzip class which accepts parameters of Map<String,Se<String>>>.

abstract class Unzip : TransformAction<Unzip.Parameters> {  // 1
    interface Parameters : TransformParameters {  // 2
        @get:Input
        var keepClassesByArtifact: Map<String, Set<String>>
    }
    @get:PathSensitive(PathSensitivity.NAME_ONLY)
    @get:InputArtifact
    abstract val inputArtifact: Provider<FileSystemLocation>
    override
    fun transform(outputs: TransformOutputs) {
        val input = inputArtifact.get().asFile
        val unzipDir = outputs.dir(input.name)
        // use keepClassesParameters
        parameters.keepClassesByArtifact.forEach { key, value ->   // 3
        }
        unzipTo(input, unzipDir)
    }
    private fun unzipTo(zipFile: File, unzipDir: File) {
    }
}

The code above does the following:

  1. Declare the parameter type.
  2. Interface the transform parameters.
  3. Use the passed parameters.

Phew! 😅 that was a lot of stuff we covered in this article. This article mainly explains about TransformAction with Kotlin DSLs and the contruction process, we still need to understand and the basic usage of principle of Android Gradle Plugin (AGP).

I hope you guys have something learnt from it. Thank you being here and keep reading!