5 min read

Making AI Review Your Gradle Build — The Unglamorous Win

Build files are the least-reviewed, highest-impact code in an Android repo. Auditing them is a near-perfect task for AI: dense, declarative, and full of implications nobody memorises. The audit prompt, the five findings it produces most often, and how to verify.

AIGradleAndroidBuild PerformanceEngineering Workflow
HR

Hessam Rastegari

Senior Android Developer · 12 years shipping Android

TL;DRbuild.gradle.kts determines how fast every engineer's day is, how large your APK ships, and whether builds are reproducible — and it gets a fraction of the review attention a ViewModel gets. Auditing it is an unusually good fit for AI: the work requires knowing what a few hundred flags do, not cleverness. Ask for concrete effects, then verify with a build scan. Never apply a build change you haven't timed.

Why build files go unreviewed

Three reasons, all cultural:

  1. Nobody feels qualified. Gradle knowledge is unevenly distributed, so reviewers skim the build file and comment on the Kotlin.
  2. It works. A build file that produces a working APK looks correct. Slow, bloated, and non-reproducible all look identical to correct.
  3. The feedback loop is long. A flag that disables an optimisation costs you 20 seconds per build forever, which nobody attributes to that line.

Meanwhile it's leveraged code: one line affects every engineer, every build, every release.

Why AI is well suited to it

This isn't a task requiring insight. It requires knowing what isMinifyEnabled, proguard-android-optimize.txt, api vs implementation, kapt vs ksp, org.gradle.configuration-cache, and two hundred other switches actually do — and noticing when a combination is contradictory.

That's boring breadth. It's precisely what a model has and a human doesn't hold in working memory, and unlike application code there's no product context it's missing.

The audit prompt

Here are my build.gradle.kts (app + one library module), gradle.properties and
libs.versions.toml.

Point out:
1. Configuration that silently disables an optimisation.
2. Dependencies declared with the wrong configuration (api vs implementation,
   compileOnly, debugImplementation).
3. Anything that breaks the configuration cache or build cache.
4. Anything non-deterministic across machines or across time.
5. Deprecated or superseded setups (kapt where KSP exists, old plugin DSL).

For each finding: the concrete effect (size, build time, correctness),
and how I'd verify it. Rank by impact. Mark anything you're unsure about.

The last two lines matter as much as the list. "Rank by impact" prevents a flat wall of nitpicks, and "mark anything you're unsure about" surfaces the guesses — Gradle has enough version-specific behaviour that a confident wrong answer is easy to produce.

What it finds most often

proguard-android.txt instead of proguard-android-optimize.txt. The non-optimising default turns off R8's optimisation pass. Everything still works, the APK is bigger, startup is slower, and this has been true silently for years. The single most common finding.

api where implementation belongs.

dependencies {
    api(libs.retrofit)              // leaks Retrofit onto every consumer's compile classpath
    implementation(libs.okhttp)     // correct: an internal detail
}

api puts the dependency on the compile classpath of every module that depends on you, so a Retrofit version bump recompiles the world. In a 40-module project this is one of the largest available incremental-build wins.

Non-deterministic version codes.

versionCode = (System.currentTimeMillis() / 1000).toInt()   // every build differs

Reproducible builds become impossible and every cache lookup misses. Derive it from a tag or a CI build number instead.

Environment-dependent config with no default.

buildConfigField("String", "API_URL", "\"${System.getenv("API_URL")}\"")

Works on CI, produces "null" for every new hire on their first day. A default with a clear warning costs one line.

kapt left behind after a KSP migration. One remaining kapt dependency keeps the entire kapt stub-generation round alive, so you pay the cost you thought you'd removed.

Verify — always

A model can tell you a flag is wrong; it cannot tell you what it's worth on your build. Measure:

./gradlew --profile clean :app:assembleRelease
./gradlew :app:assembleDebug --scan

Change one thing at a time, and compare against a baseline you captured first. Build changes are unusually easy to convince yourself about — the build "feels" faster the moment you've spent an hour on it.

For APK size, bundletool or the APK Analyzer gives you a before/after that isn't a feeling.

The limits

It will occasionally recommend flags that were removed in your Gradle version, or repeat advice that was true in 2021. It has no idea that your api declaration is deliberate because three modules genuinely need that transitive type. Treat findings as leads with stated effects — which is exactly why the prompt demands the effect and a verification method rather than a verdict.

The habit

Run this audit once a quarter, and whenever you upgrade AGP or Gradle. It takes ten minutes, it's the cheapest performance work available, and it targets the one file in your repository that affects everyone on the team every single day.

Keep reading