16 - Android / Flutter Troubleshooting
Every Windows-specific Gradle and Android build issue we hit while bringing up the eKYC Flutter app, documented with the exact error, the root cause, and the fix. If you're seeing anything weird — search this page first before reaching for Stack Overflow.
Build build-time error during flutter build or flutter run ·
Runtime app installs but crashes or misbehaves ·
Windows Windows-host specific (works fine on macOS/Linux) ·
Env environment / network configuration issue
Issues covered on this page
- Gradle "Could not move temporary workspace"
- Cached JARs locked by another Java process
- Cache-corruption error recurs even after clean
- Relocate Gradle cache with
GRADLE_USER_HOME - NDK version mismatch warning
- Kotlin incremental compiler crash (cross-drive paths)
- "unknown property 'android'" on plugin subprojects
- FormatException at character 1 <!DOCTYPE...>
- Emulator can't reach host localhost
- Release APK blocks cleartext HTTP
- Prevention checklist for new Windows dev machines
1. Gradle "Could not move temporary workspace" Build Windows
android { }
DSL extension never got registered.
cd D:\MO_Project\ekyc\flutter_app\android
.\gradlew --stop
cd ..
rmdir /s /q "%USERPROFILE%\.gradle\caches\8.11.1\transforms"
rmdir /s /q "%USERPROFILE%\.gradle\caches\transforms-4"
C:\flutter\bin\flutter clean
C:\flutter\bin\flutter pub get
C:\flutter\bin\flutter build apk --release --dart-define=ENV=prod
If it fails again with the same error, jump to issue #3 below.
2. Cached JARs locked by another Java process Build Windows
The file names are the giveaway: kotlin-gradle-tooling.jar,
android-extensions-ide.jar, util_rt.jar — these
are all used by IDE integration, not a plain CLI
build. That means an IDE (Android Studio / IntelliJ / VS Code with
the Dart-Flutter plugin) is running in the background and holding
the JARs via its own long-lived Gradle daemon and Kotlin daemon.
These IDE-spawned daemons do NOT respond to
gradlew --stop because they belong to a different
Gradle user directory / different Java process tree.
Close the IDE fully (File → Exit, not just close the window), then nuke all Java processes.
REM See what Java processes are still alive
tasklist /v /fi "imagename eq java.exe"
tasklist /v /fi "imagename eq javaw.exe"
REM Kill them all (safe if you're not running another Java app)
taskkill /f /im java.exe
taskkill /f /im javaw.exe
REM Now the rmdir from issue #1 will succeed
rmdir /s /q "%USERPROFILE%\.gradle\caches\8.11.1\transforms"
Prevention: don't run flutter build
from CLI while Android Studio is open on the same project. Pick one
— either use Android Studio's built-in Build → Flutter → Build APK
(reuses the running daemon), or fully close the IDE before building
from CLI.
3. Cache-corruption error keeps coming back even after clean Build Windows
Same move-failure as issue #1, but on a fresh transform
hash — which rules out stale cache as the cause. Something is
interfering with .gradle\caches live while
Gradle runs, interrupting the atomic rename. On Windows dev boxes
it's almost always one of:
- OneDrive Known Folder Move syncing your user profile
- Windows Defender real-time scanning
- Windows Search indexer opening files briefly
- Third-party AV (Norton, McAfee, Kaspersky, Sophos)
All of these grab transient file handles that race with Gradle's rename operation.
REM Is your profile actually on OneDrive?
echo %OneDrive%
echo %USERPROFILE%
If %OneDrive% points at
C:\Users\...\OneDrive — yes, it's syncing. Move on to
the permanent fix in issue #4. If it doesn't, the problem is
Defender or AV — add exclusions (see below) and/or relocate the
Gradle cache anyway.
4. Relocate Gradle cache with GRADLE_USER_HOME Build Windows
Moving the Gradle cache off C:\Users entirely bypasses
OneDrive, Defender profile scans, and any user-profile weirdness in
one shot. The cache lives on a plain drive folder where nothing
messes with it.
REM 1. Create the new cache folder on a non-synced drive
mkdir D:\gradle-cache
REM 2. Set GRADLE_USER_HOME permanently (writes to user env,
REM but does NOT update the CURRENT cmd window)
setx GRADLE_USER_HOME "D:\gradle-cache"
REM 3. Also set it for the current session so you can test immediately
set GRADLE_USER_HOME=D:\gradle-cache
REM 4. Verify
echo %GRADLE_USER_HOME%
Add Defender exclusions
Windows Security → Virus & threat protection → Manage settings → Exclusions → Add or remove exclusions → Add an exclusion → Folder:
D:\gradle-cacheD:\MO_Project\ekyc\flutter_appC:\flutter
cd D:\MO_Project\ekyc\flutter_app
C:\flutter\bin\flutter clean
C:\flutter\bin\flutter pub get
C:\flutter\bin\flutter build apk --release --dart-define=ENV=prod
First build takes ~8-12 minutes because Gradle re-downloads everything into the new cache. After that, builds are back to normal speed.
For best results, move Dart's pub cache off C: too so all three caches (Gradle, pub, project) share one drive. This also fixes issue #6 below.
setx PUB_CACHE "D:\pub-cache"
REM Close cmd and reopen (setx only applies to new windows)
cd D:\MO_Project\ekyc\flutter_app
C:\flutter\bin\flutter clean
C:\flutter\bin\flutter pub get REM re-downloads everything into D:\pub-cache
5. NDK version mismatch warning Build
12 plugins declared they need NDK 27 but our
android/app/build.gradle.kts pins NDK 26. NDK versions
are backward-compatible per Flutter's guidance, so the build still
succeeds — but the warning is persistent and a future Play Store
lint check may escalate it.
Edit android/app/build.gradle.kts and pin the highest
version:
android {
namespace = "com.example.ekyc_app"
compileSdk = flutter.compileSdkVersion
ndkVersion = "27.0.12077973" // <- add this
// ...
}
Make sure NDK 27 is installed (the first build will auto-download it if not — takes a few minutes):
sdkmanager "ndk;27.0.12077973"
6. Kotlin incremental compiler crash (cross-drive paths) Build Windows
Known bug in Kotlin's incremental compilation on Windows. When
the project is on one drive (D:) but pub cache is on
another (C:), Kotlin IC calls
kotlin.io.FilesKt.relativeTo() to compute a relative
path between them. Windows File.relativeTo() throws
IllegalArgumentException because you can't relativize
paths across drive roots — there's no common ancestor.
Open android/gradle.properties and add:
# Workaround for Kotlin IC crash when pub cache and project are on
# different drives (Windows). See frontend/16-android-troubleshooting.
kotlin.incremental=false
kotlin.incremental.multiplatform=false
kotlin.incremental.js=false
This is a targeted workaround — full rebuilds are unaffected, only incremental re-compile is a tiny bit slower.
Move PUB_CACHE to the same drive as the project so
Kotlin IC can compute paths normally. Then you can re-enable
incremental compilation.
setx PUB_CACHE "D:\pub-cache"
REM Close cmd, reopen
cd D:\MO_Project\ekyc\flutter_app
C:\flutter\bin\flutter clean
C:\flutter\bin\flutter pub get
After pub cache is on D:, remove the kotlin.incremental=false
line from gradle.properties for snappier rebuilds.
7. "Could not get unknown property 'android' for project ':xyz_android'" Build
This is almost never about the plugin itself. It's a cascading
failure: an earlier build step (usually a transform cache corruption
— issue #1) killed the plugin's build.gradle.kts
halfway through execution. The apply plugin: 'com.android.library'
line runs, but the transform explosion prevents the android { }
DSL extension from being registered. The very next reference to
android throws "unknown property".
You don't fix this error directly — fix issue #1 (the transform
cache problem) and this error disappears on its own. If you've done
the full reset in issue #4 and it still appears, something is very
wrong — check that the plugin actually exists in
%PUB_CACHE%\hosted\pub.dev\, re-run
flutter pub get, and check
flutter doctor -v for red flags.
8. FormatException at character 1 <!DOCTYPE...> Runtime Env
The app's ApiService called
jsonDecode() on a response body that is HTML, not JSON.
The server returned a default 404 page, an nginx welcome page, or an
IIS error page — the <!DOCTYPE ... EN> string is
the XHTML doctype used by old server error templates.
Most common reason: the URL in lib/config/api_config.dart
for the chosen --dart-define=ENV=... value doesn't point
at a running eKYC backend.
curl -v https://your-backend-url/api/v1/journey/init?device_type=DESKTOP
- JSON blob → backend is fine, problem is in the Flutter app
- HTML / 404 / nginx welcome → backend is wrong, fix the URL
- Connection refused / timeout → backend is down or unreachable
Edit lib/config/api_config.dart and update the URL
for your target environment, or build against the correct ENV:
// Local dev
flutter build apk --release --dart-define=ENV=dev
// UAT
flutter build apk --release --dart-define=ENV=uat
// Prod
flutter build apk --release --dart-define=ENV=prod
Cross-link: See 09 - Config & Environments for the full environment flag reference.
9. Emulator can't reach host localhost Runtime Env
Inside the Android emulator, localhost (and
127.0.0.1) means the emulator itself — not your Windows
host. If the app's ENV=dev URL is http://localhost:5000
and your backend runs on the Windows host, the emulator's HTTP call
goes nowhere and you get a connection error or the HTML
404 from issue #8.
adb reverse (recommended)
Forward the emulator's port 5000 back to your host. The existing
localhost:5000 URL in api_config.dart then just works
without any code change.
REM 1. Run backend bound to all interfaces
cd D:\MO_Project\ekyc\backend\src\MO.Ekyc.Api
dotnet run --urls http://0.0.0.0:5000
REM 2. In a second cmd window: forward the port
adb devices
adb reverse tcp:5000 tcp:5000
adb reverse --list
REM 3. In a third cmd window: run the app
cd D:\MO_Project\ekyc\flutter_app
C:\flutter\bin\flutter run --dart-define=ENV=dev
Option B — use the magic 10.0.2.2 IP
The Android emulator reserves 10.0.2.2 as an alias for
the host machine's loopback. Change the dev URL in
api_config.dart:
case 'dev':
default:
return 'http://10.0.2.2:5000';
Option C — physical device on LAN
Use the host machine's actual LAN IP (find with ipconfig):
case 'dev':
return 'http://192.168.1.42:5000'; // your host LAN IP
Make sure the backend binds to 0.0.0.0 (not
localhost) and that Windows Firewall allows port 5000 inbound.
10. Release APK blocks cleartext HTTP Runtime
Android 9+ blocks plain HTTP by default in release builds. Debug
builds are exempt (via a generated network security config that
allows localhost and 10.0.2.2) — release builds are not.
If you build with
flutter build apk --release --dart-define=ENV=dev and
the dev URL is http://..., the app installs but every
API call fails silently with a connection error.
flutter run for local testing
For local dev, just don't build release APKs. Debug mode is faster, has hot reload, shows debug logs, and allows cleartext HTTP out of the box:
cd D:\MO_Project\ekyc\flutter_app
C:\flutter\bin\flutter run --dart-define=ENV=dev
If you really need a release APK against a local HTTP backend
Temporarily allow cleartext in android/app/src/main/AndroidManifest.xml:
<application
android:label="ekyc_app"
android:usesCleartextTraffic="true"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
Never ship a production release build with
usesCleartextTraffic="true". It opens the app to
man-in-the-middle attacks. Either remove the attribute before
building prod, or use a build-variant-scoped
network_security_config.xml that only allows cleartext
in debug/dev flavours.
11. Prevention checklist for a new Windows dev machine Windows
-
Move all build caches off
C:\Users:
Close and reopen every cmd window to pick up the new values.setx GRADLE_USER_HOME "D:\gradle-cache" setx PUB_CACHE "D:\pub-cache" setx ANDROID_USER_HOME "D:\android" setx ANDROID_SDK_ROOT "D:\android-sdk" -
Add Windows Defender exclusions for:
D:\gradle-cacheD:\pub-cacheD:\android,D:\android-sdkD:\MO_Project\ekyc(the whole project root)C:\flutter(the Flutter SDK install)
-
Disable OneDrive Known Folder Move or make sure
your
C:\Users\<you>isn't redirected into OneDrive. OneDrive + Gradle cache is a disaster combination. - Don't run CLI build + IDE concurrently on the same Flutter project. The IDE spawns its own long-lived Gradle and Kotlin daemons that hold file handles indefinitely.
-
Never commit
.gradle/,build/,.dart_tool/. They're already in.gitignoreat the repo root — keep them that way. -
Don't install Flutter inside
C:\Program Files. It's a protected path andflutter upgradewill fail. UseC:\flutterorD:\flutter. -
Stop and restart
gradlew --stopbefore any long operation — clean, pub get, upgrade. This clears any zombie daemon that might hold the cache.
See also
- 01 - Getting Started — fresh setup walkthrough
- 09 - Config & Environments —
--dart-define,ApiConfig, env switching - 10 - Debugging — DevTools, breakpoints, network inspection
- 15 - Master Dev Help — one-page "which file do I open?" lookup