Pocket Studio Academy
HomePart 22.6

Why the first build takes so long

Free lesson8 min read·4 questions

Your first build took minutes and your second took seconds, and nothing was broken. Here is exactly where those minutes went, why later builds skip almost all of it, and why a small dependency list matters far more on a phone than on a laptop.

Nothing is wrong with your phone

In Lesson 0.3 you pressed Run for the first time and waited. Possibly for several minutes. The phone got warm. You may reasonably have wondered whether it had hung.

Then you changed one line, pressed Run again, and it finished in a few seconds.

Both of those are correct behaviour, and the gap between them is not a mystery. A first build does four expensive things that a second build does almost none of. Once you know which four, you also know how to keep every build after the first one fast — which, on a device with no fan, is a skill worth having.

Think of it like this

Think about cooking your first meal in a brand new flat.

The kitchen is empty. Before you can start you need a pan, a wooden spoon, salt, oil, and a sharp knife. So the first meal takes an hour and a half, and most of that is a trip to the shops and putting things away in cupboards you have never opened.

The second meal takes twenty minutes. Same recipe, same cook. The difference is that the pan is already in the cupboard, the oil is already on the shelf, and you already know where the sharp knife lives.

Nothing got faster. The shopping just already happened.

Where the first minutes actually go

1. Downloading Gradle itself

Your project does not contain . It contains the : a small script and a file naming an exact version. The first time you build, that script downloads the real thing — the Gradle 8.11.1 distribution, which is a zip of roughly 130 MB — and unpacks it.

This happens once per Gradle version, per device. Every project you ever build with Gradle 8.11.1 reuses it. That is one of the reasons this course pins a single version everywhere.

2. Downloading every library

Then Gradle works through the shopping list. Not just your eight lines of — every one of their dependencies too. Dice Duel resolves to about forty modules: the Compose libraries, AndroidX core, lifecycle, activity, coroutines.

That is hundreds of individual files, each fetched over the network. Add the and the Kotlin compiler, which are themselves substantial downloads.

These land in a shared cache on your device and are never downloaded again — not for this project, and not for the next one either.

3. Unpacking and transforming them

Android libraries do not arrive in the shape the build needs. Each one has to be unpacked, its resources extracted, its code converted. Gradle keeps the results in a transforms folder next to the downloads, and this step alone can take longer than the compile on a first build.

Also cached. Also never repeated for the same library version.

4. Compiling everything, cold

Finally the actual work, with nothing to reuse:

  • The Kotlin compiler starts up and compiles every file in your project.
  • compiles every resource and builds the lookup table.
  • The merges every library's manifest.
  • turns all of that into .

Four sizeable programs, each starting from nothing, each doing the maximum amount of work it will ever do.

A real number, from a real phone

Focus Flow — the largest app in this course, with Room, DataStore, navigation and notifications — was built from a clean import on a physical Android phone in Pocket Studio.

The first build took 270.8 seconds, about four and a half minutes, and produced a 19.58 MB debug with zero issues. That is a normal, healthy first build, not a fault.

Your own number will differ. A faster phone, a warmer cache or a smaller app will all pull it down. What matters is the shape: minutes the first time, seconds afterwards.

Why you see two different times

That screen reports 4s and 13.585s for the same rebuild, and both are honest.

BUILD SUCCESSFUL in 4s is Gradle timing its own work. Build succeeded in 13.585s is Pocket Studio timing the whole button press — starting Gradle, talking to the daemon, and collecting the result afterwards.

The gap between them is fixed overhead. It barely moves whether the build is tiny or huge, which is why it looks enormous next to a 4-second rebuild and invisible next to a 270-second one.

Why the second build is so much faster

Four different mechanisms, all working at once.

The downloads are already there. Steps 1, 2 and 3 above simply do not happen. On a second build they cost zero.

Gradle checks what actually changed. Every declares its inputs and its outputs. Before running one, Gradle fingerprints the inputs and compares them with last time. If nothing changed, the task is skipped and the previous output is reused. That is the , and you can watch it working in the last line of every build:

text
41 actionable tasks: 3 executed, 38 up-to-date

Thirty-eight of the forty-one steps were skipped entirely.

The compiler only recompiles what it must. Change one function in one file and the Kotlin compiler works out which files could possibly be affected and recompiles only those. This is an .

The daemon is already warm. The from your last build is still running: the has started, the classes are loaded, and the code inside it has been optimised by running. A cold JVM start alone can cost several seconds.

21:22▲ ▮
Build output
> Task :app:preBuild UP-TO-DATE
> Task :app:mergeReleaseResources UP-TO-DATE
> Task :app:...ListingFileRedirect UP-TO-DATE
BUILD SUCCESSFUL in 4s
50 actionable tasks: 1 executed,
49 up-to-date
Build succeeded in 13.585s
One step ran. Forty-nine were already done. The same project took 270 seconds the first time.

Real output from a rebuild of Focus Flow in Pocket Studio. Forty-nine of fifty steps did no work at all.

Why this matters double on a phone

Every word above is true on a laptop too. Here is what is different in your hand.

There is no fan. A phone that gets hot protects itself by slowing its own processor down. This is called thermal throttling, and it means a long build gets slower as it goes. The last minute of a ten-minute build can be running at half the speed of the first. A laptop with a fan simply does not have this problem.

There is a battery. A full cold build is one of the heaviest things you can ask a phone to do. Plug in before a big one.

There is less memory. The Gradle daemon is configured to use up to 2 GB:

text
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8

If the phone runs short, Android will kill the daemon mid-build — and you get an error that looks alarming but simply means "out of memory". It is in the Error Doctor below.

There is less storage. The Gradle distribution, the downloaded libraries and the transformed copies add up to well over a gigabyte before your app contributes anything.

The practical conclusion: keep the list short

Everything above scales with how many libraries you use.

Each extra means more to download, more to unpack, more to merge, more to dex, and a bigger for Gradle to reason about on every single build. A library that also generates code — anything using — adds a whole compile step of its own.

This is not theory. It is why the three apps in this course are built the way they are:

ChoiceThe easy wayWhat this course does
Dice facesAn image library and six PNGsDrawn with Compose [[canvasCanvas]]
Focus Flow's stats chartA charting libraryDrawn with Compose Canvas
Wiring objects togetherA dependency-injection libraryPlain Kotlin constructors
Loading imagesAn image-loading libraryNot needed — no remote images

Dice Duel declares eight dependencies. Adding a charting library and a dependency-injection framework would roughly double the graph, add a code-generation step, and make every build on your phone slower and hotter for the rest of the course.

You can always add a library later, once you can feel what it costs. Starting without one is free.

Four things not to do

  • Do not run Clean. ./gradlew clean deletes exactly the outputs that let Gradle skip thirty-eight steps. It is a fix for almost nothing and it guarantees a slow next build. Try it only when the build output is clearly nonsense.
  • Do not change Gradle versions casually. A new version means a fresh 130 MB download and a fresh cache of transformed libraries.
  • Do not kill the daemon between builds. Leaving it alive is the point of it.
  • Do not build with fifteen other apps open. Free the memory first; you will get it back in build time.
Try it in Pocket Studio
  1. Open Pocket Studio, tap Projects, open your Part 0 app.
  2. Tap Build and press Run. Note the time on the BUILD SUCCESSFUL in … line and the actionable tasks line under it.
  3. Press Run again without changing anything. Almost every task should now say UP-TO-DATE, and the build should finish in a second or two.
  4. Tap Editor, change one piece of visible text, and press Run again. Compare the actionable tasks line: a handful executed, the rest up to date.
  5. Open the Terminal in the Build tab and run ./gradlew :app:assembleDebug --profile. When it finishes it prints the path to an HTML report. Open it from the file tree to see exactly which tasks took the time.
  6. Only if you are curious, and while plugged in: run ./gradlew clean and then build again. That is roughly what a first build feels like. Now you know never to do it casually.
Error Doctor6 common errors
[CXX5106] NDK was located by using ndk.dir property. This method is deprecated and will be removed in a future release.
MeansA warning, not an error — your build succeeded. Pocket Studio points Gradle at its bundled native toolchain using an older setting. None of the apps in this course contain native code, so this has nothing to do with anything you wrote.
FixNothing to fix. Ignore it. Judge a build by the line that says BUILD SUCCESSFUL and by whether an APK appeared, not by the absence of warnings — a healthy Android build is often noisy.
Could not download gradle-8.11.1-bin.zip (https://services.gradle.org/distributions/gradle-8.11.1-bin.zip) > Read timed out
MeansThe very first step — fetching Gradle itself — failed part way through. It is a 130 MB download, so a patchy connection catches it more often than anything else.
FixGet onto stable Wi-Fi and build again. It resumes from a clean start, not from nothing useful — and once it succeeds, it never has to happen again on this device.
Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed)
MeansAndroid killed the Gradle daemon mid-build to reclaim memory. This is the single most common mystery failure when building on a phone, and it is not your code's fault.
FixClose your other apps and build again. If it repeats, lower the daemon's memory in gradle.properties — try org.gradle.jvmargs=-Xmx1536m — so Android is less tempted to kill it.
Expiring Daemon because JVM heap space is exhausted FAILURE: Build failed with an exception. > Java heap space
MeansThe opposite problem: the daemon ran out of the memory it was allowed, rather than being killed from outside.
FixRaise the limit in gradle.propertiesorg.gradle.jvmargs=-Xmx3072m — but only if the phone actually has the RAM spare. On a 4 GB phone, closing other apps is the better move.
Timeout waiting to lock artifact cache (/data/…/caches/modules-2). It is currently in use by another Gradle instance.
MeansTwo builds are running at once and both want the download cache. Usually a previous build that never finished and is still sitting there.
FixStop the running build with the Stop button in the Build tab, wait a few seconds, then build again. If it persists, run ./gradlew --stop in the Terminal to shut every daemon down and start fresh.
java.io.IOException: No space left on device
MeansExactly what it says. Between the Gradle distribution, the library cache and the transformed copies, a first build needs a surprising amount of room.
FixFree up 3 GB or so and build again. Photos and videos are usually the fastest win. Deleting the build folder inside your project reclaims a little, at the cost of a slower next build.
Recap
  • A first build pays four one-time costs: downloading itself, downloading every , unpacking and transforming them, and compiling everything cold.
  • Later builds skip nearly all of it: the reuses unchanged task outputs, compilation is , and the is already warm.
  • 41 actionable tasks: 3 executed, 38 up-to-date is the line that tells you it is working.
  • On a phone this matters double: no fan means thermal throttling, and memory and storage are tighter. A smaller builds faster and cooler.
  • This course's apps deliberately use very few dependencies — the dice faces and the stats chart are drawn on precisely so no library is needed.
  • Avoid clean. It throws away the very thing that makes builds fast.
  • Next: the three SDK numbers in build.gradle.kts — what each one really controls, and why mixing them up is the most common misunderstanding in Android.