Tony Robalik

Simplify your Android library development with one line of configuration

A cat

Photo by Núria Fosas Forns on Unsplash

Fixing Google's strange decision to make all Android libraries multi-variant by default

To save the suspense, here's the "one line of configuration":

settings.gradle.kts

plugins {
  id("com.autonomousapps.dedebug") version "0.1"
}

(It'll be a single line if you already have a plugins block in your settings script.)

Back at the dawn of time, it was decided that Android development should be Complicated. Android was (is! is!) special. We are grateful for our many Variants. Build Types should be at least two in number, and Product Flavors as varied as wildflowers in a field in springtime, yes verily.

But uh, what if it wasn't? Just sometimes! For special occasions? Could we disable the default completely useless, time-wasting, carpal-tunnel-inducing additional variants? Oh, we can? Cool.

Introducing the DeDebug Gradle plugin

⭐ me on Github

Here's the full configuration:

settings.gradle.kts

plugins {
  id("com.autonomousapps.dedebug") version "0.1"
  id("com.android.library") version "9.3.1" apply false

  // Optional, if you have Kotlin/JVM modules
  id("org.jetbrains.kotlin.jvm") version "2.4.10" apply false

  // Include every plugin here and you simplify your class
  // loading situation.
  ... also *every other plugin* ...
}

dedebug {
  include(...) // optional: only include these modules
  exclude(...) // optional: exclude some modules
}

The DeDebug plugin is a settings plugin, so it must be applied in the settings script. DeDebug configures Android libraries, so com.android.library classes need to be visible to it. And due to Gradle classloading shenanigans, the simplest way to achieve that is to load the AGP plugins in the same classloader. You might think this is an annoying constraint, but in fact it's a best practice—but I repeat myself.[1]

What does this get me?

Terrible question. Do you even Android? (This is not not a joke.)

In addition to the classloader-related forcing function, DeDebug provides two major benefits:

First, it eliminates the "debug" build type in every Android library in your build, except those you explicitly opt out. It also handles the slightly gnarly configuration so that Gradle variant-matching works transparently.

Second, it brings Android library modules into feature parity with JVM modules by adding support for, e.g.,[2]

./gradlew <android-library>:test --tests AcabTest

Say goodbye to Unknown command-line option '--tests'.

In addition to the above, it provides some assistance to developers used to the old workflows: testDebugUnitTest[3] and connectedDebugAndroidTest still work, but they emit a warning that developers should switch to the simpler test and connectedAndroidTest tasks.

Anything else I should know?

Well, I think the implementation is kind of cool. It's Isolated Projects-compatible, which is great because IP is officially "incubating" with the release of Gradle 9.7.0. This wasn't entirely straightforward because all configuration is in the settings script, but the configuration is global: it impacts potentially every module in the project.

It provides enhanced test support. It parses the command line in order to add --tests support to Android's test task. For those that don't know, test in an Android context is a so-called "lifecycle" task: it does nothing itself, but it depends on testDebugUnitTest and testReleaseUnitTest, which are the "real" test tasks. Those latter support --tests (which is defined as @Option setTaskNameIncludePatterns()), but test, as a lifecycle task, does not. Additionally, Gradle will fail if CLI options are included and the exact task that supports those options isn't present on the command line (it can't just be in the task graph). DeDebug supports this pattern by parsing the command line, extracting the user-requested tests, and configures all "real" Test tasks with those requests tests.

Other than that, it's a pretty simple single-module Gradle plugin. In fact, I think the basic structure is so, well, basic that it's a good template. I'll be extracting that template as a separate project in the future to make it easier for others to create Gradle plugins of their own.

Happy Gradling!


  1. No really, loading all build dependencies in a single classloader (the settings classloader) is a really good idea. Dramatically simplifies your build classpath, eliminating a whole class of whole-day-destroying errors. It also reduces your builds' memory consumption. ↩︎

  2. assertTrue("all cats are beautiful") ↩︎

  3. Real ones' shell histories are full of tDUT. ↩︎