---
name: build-android
description: Build and deploy XerahS Android apps (Kotlin/Mobile.Kt, Avalonia, MAUI) to emulator/device via adb. Covers JAVA_HOME for Gradle, Compose KeyboardOptions/foundation, file locks, EmbedAssembliesIntoApk, init white-screen, single-node builds (-m:1), and cold-boot emulator. Never wait more than 5 minutes for a build—if it exceeds that, treat as failure and fix locks or parallelism.
metadata:
keywords:
- build
- android
- kotlin
- mobile.kt
- gradle
- compose
- maui
- avalonia
- adb
- apk
- deploy
- JAVA_HOME
- KeyboardOptions
- file-lock
- EmbedAssembliesIntoApk
- white-screen
- init
- cold-boot
- emulator
- no-snapshot-load
- single-node
- m:1
---
You are an expert Android build and deploy specialist for XerahS mobile (Kotlin/Mobile.Kt, Avalonia, and MAUI).
Follow these instructions when building or deploying Android apps. **Never let a build wait more than 5 minutes** without concluding something is wrong (locks, stuck process, or wrong command).
Build XerahS.Mobile.Ava or XerahS.Mobile.Maui for Android and deploy via adb.
Avoid file locks by using single-node builds and killing lingering dotnet processes.
If a build runs longer than 5 minutes, stop and fix the cause (locks, parallelism, or stale processes).
Ensure MAUI APK works when installed via adb (EmbedAssembliesIntoApk).
build/android/build-and-deploy-android-maui.ps1
build/android/build-and-deploy-android-ava.ps1
com.getsharex.xerahs
com.getsharex.xerahs.mobile
%LOCALAPPDATA%\Android\Sdk\platform-tools\adb.exe (adb is often not in PATH; use full path)
src\mobile-experimental\XerahS.Mobile.Maui\bin\Debug\net10.0-android\com.getsharex.xerahs-Signed.apk
src\mobile-experimental\XerahS.Mobile.Ava\bin\Debug\net10.0-android
src\mobile\android
src\mobile\android\app\build\outputs\apk\debug\app-debug.apk
## 5-minute build rule (critical)
**Do not wait more than 5 minutes for an Android build to complete.** A normal single-node Android build finishes in about 2–5 minutes. If the build has not completed within 5 minutes:
1. **Treat it as a failure** — something else is wrong.
2. **Stop the build** (cancel or let the command timeout).
3. **Fix the cause** before retrying:
- **Lingering processes**: A previous `dotnet` build may be holding the APK or DLLs. Find and stop the process (see "Pre-build: release locks" below).
- **Clean failing**: If `dotnet clean` fails with "file in use", the lock is often the APK or a DLL; the error message names the process (e.g. ".NET Host (PID)").
- **Parallelism**: Use `-m:1` so only one project builds at a time and `ShareX.ImageEditor`/plugin DLLs are not raced.
Do **not** increase the timeout to 10+ minutes. Fix locks and parallelism instead.
---
## Kotlin (src/mobile/android): prerequisites and first-build lessons
The Kotlin app uses **Gradle** (not dotnet). The project root is **`src/mobile/android`** (where `settings.gradle.kts` and `gradlew.bat` live).
### 1. JAVA_HOME is required
**First build often fails with:** `JAVA_HOME is not set and no 'java' command could be found in your PATH`.
- Gradle needs a JDK. On Windows, **adb** and **java** are often not in PATH; the IDE uses the embedded JBR.
- **Fix:** Set `JAVA_HOME` to the JDK used by Android Studio before running Gradle, then run the build in the same process (e.g. one PowerShell command that sets env and invokes gradlew):
```powershell
$env:JAVA_HOME = "C:\Program Files\Android\Android Studio\jbr"
cd "src\mobile\android"
.\gradlew.bat assembleDebug
```
- If the repo is under a path with spaces (e.g. `ShareX Team\XerahS`), use quoted paths and ensure `cd` is to the Kotlin project root.
### 2. Compose KeyboardOptions: correct import and dependency
**Symptom:** `Unresolved reference: KeyboardOptions` when compiling a module that uses `KeyboardOptions` in a Compose `TextField`/`OutlinedTextField`.
- **Wrong:** `import androidx.compose.ui.text.input.KeyboardOptions` — that package does not expose `KeyboardOptions` in the way the compiler resolves for the dependency set in use.
- **Right:** `import androidx.compose.foundation.text.KeyboardOptions`. `KeyboardCapitalization` stays `androidx.compose.ui.text.input.KeyboardCapitalization`; `KeyboardType` is `androidx.compose.ui.text.input.KeyboardType`.
- **Module dependency:** The module that uses `KeyboardOptions` (e.g. `feature:settings`) must depend on **Compose Foundation**, or the build will still fail with "Unresolved reference: KeyboardOptions". In that module’s `build.gradle.kts`:
```kotlin
implementation(libs.compose.foundation)
```
- **URL / no auto-capitalize:** For URL or domain fields, use `KeyboardType.Uri` together with `KeyboardCapitalization.None` so the soft keyboard does not auto-capitalize; some IMEs ignore capitalization when the type is already `Uri`.
### 3. adb not in PATH
**Symptom:** `The term 'adb' is not recognized` when running install or logcat from a script or terminal.
- Use the full path to adb, e.g. `$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe`, or set `ANDROID_HOME` / `PATH` so that `adb` is found. The build skill assumes adb is invoked via full path when PATH is not configured.
### 4. Kotlin build and deploy (summary)
| Step | Command / path |
|------|----------------|
| Project root | `src\mobile\android` |
| Build | Set `JAVA_HOME` then `.\gradlew.bat assembleDebug` |
| APK | `app\build\outputs\apk\debug\app-debug.apk` |
| Install | `adb install -r ` |
---
## Pre-build: release locks
File locks are the main cause of Android build failures. **Before building**, ensure no previous build is still running and clean can run.
### 1. Identify what is locked
- **Clean fails** with `The process cannot access the file '...' because it is being used by another process`. The message often says **which process** (e.g. `.NET Host (27788)` or `VBCSCompiler`).
- **Build fails** with `CompileAvaloniaXamlTask` / `Cannot open '...ShareX.ImageEditor.dll' for writing` or `...XerahS.AmazonS3.Plugin.dll ... being used by another process` — usually **parallel MSBuild** racing on the same output.
- **Build fails** with `XA0142` / `llvm-objcopy.EXE: error: permission denied` or **`XAWAS7024: The file is locked by: ".NET Host (PID)"** on paths like `obj\Debug\net10.0-android\android-arm64\wrapped\lib_*.dll.so` — a previous build’s .NET Host is still holding wrapped assembly outputs. Stop that PID, then clean and rebuild.
### 2. Stop the process holding the lock
If the error names a PID (e.g. 27788):
```powershell
Stop-Process -Id 27788 -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
```
If you see many `dotnet` processes and a recent build was run, consider stopping **only** the ones that are build workers (e.g. those that have been running for a long time). Avoid killing the IDE’s dotnet (e.g. OmniSharp) if possible; closing other terminals/builds first is safer.
### 3. Use single-node build to avoid races (required)
**NEVER run two Android (or solution) builds at the same time.** `ShareX.ImageEditor` and the AmazonS3 plugin share outputs that the Avalonia XAML task and MSBuild copy; parallel MSBuild nodes race on the same DLLs and Android wrapped `.so` files and cause lock errors. **That is why the Android build feels slow:** we must use **`-m:1`** (single node), so the build runs sequentially with no parallelism.
- **MAUI**: The script `build-and-deploy-android-maui.ps1` already uses **`-m:1`**. Do not remove it.
- **Avalonia**: The script `build-and-deploy-android-ava.ps1` uses **`-m:1`** by default. Do not remove it.
Example:
```powershell
dotnet build $projectPath -f net10.0-android -c Debug -m:1
```
### 4. Clean after releasing locks
After stopping the locking process:
```powershell
dotnet clean "src\mobile-experimental\XerahS.Mobile.Maui\XerahS.Mobile.Maui.csproj" -f net10.0-android -c Debug -v minimal
# For Avalonia:
dotnet clean "src\mobile-experimental\XerahS.Mobile.Ava\XerahS.Mobile.Ava.csproj" -f net10.0-android -c Debug -v minimal
```
If clean still fails, manually remove `obj` folders that are locked (e.g. `ShareX.ImageEditor\src\ShareX.ImageEditor\obj`, `src\desktop\plugins\AmazonS3.Plugin\obj`, `src\mobile-experimental\XerahS.Mobile.Ava\obj\Debug\net10.0-android`, or the MAUI `obj\Debug\net10.0-android` if the APK is locked).
---
## Cold-boot emulator (optional)
To start an Android emulator with a **cold boot** (no snapshot load) before building/deploying:
1. **List AVDs:** `& "$env:LOCALAPPDATA\Android\Sdk\emulator\emulator.exe" -list-avds`
2. **Start emulator:** `Start-Process -FilePath $emulator -ArgumentList "-avd","","-no-snapshot-load" -WindowStyle Normal`
3. **Wait for device:** Poll until `adb devices` shows a line ending with `device` (e.g. up to 120 s). The build script will fail with "No emulator/device attached" if no device is present when it runs the install step.
4. **Then** run the build-and-deploy script. If the emulator is closed or disconnects during the 2–5 minute build, run `adb devices` again and restart the emulator if needed before re-running only the deploy (manual install/launch below).
---
## MAUI-specific: EmbedAssembliesIntoApk
When the MAUI app is installed via **adb install** (not via Visual Studio deploy), the APK must **contain** the .NET assemblies. By default, Debug uses **Fast Deployment**, which omits assemblies from the APK and pushes them via the IDE; so a standalone `adb install -r` results in "No assemblies found" and the app crashes after the splash.
- **Fix**: In `XerahS.Mobile.Maui.csproj`, set **`EmbedAssembliesIntoApk`** to **`true`** for Android (already done). Then rebuild; the APK will be larger and work with adb install.
- **Trade-off**: Build is slightly slower; no change to behavior once embedded.
---
## MAUI white screen / init
If the MAUI app shows a **white screen** after the logo (similar to the old Avalonia "stuck" issue):
- **Cause**: The loading page has not painted before heavy init runs (or with `EmbedAssembliesIntoApk`, first frame is slower). Starting init too soon leaves the user seeing a blank screen.
- **Fix**: In `MainActivity.cs`, **defer** calling `InitializeCoreAsync` by **~400 ms** (e.g. `Task.Run` + `Task.Delay(400)` + `MainThread.BeginInvokeOnMainThread`). Do not call it immediately in `OnCreate`. See `developers/lessons-learnt/android_avalonia_init_fix.md` (MAUI section).
---
## Avalonia-specific: host Content
Avalonia Android had a bug where **`parent.Content = null`** in MainActivity cleared the host of `MainView`, so the whole UI disappeared even though init and navigation completed. **Do not** set the host’s `Content` to null. See `developers/lessons-learnt/android_avalonia_init_fix.md`.
---
## Build and deploy commands
### Kotlin (src/mobile/android)
Set JAVA_HOME and build from the Kotlin project root (see "Kotlin: prerequisites and first-build lessons" above). Then install with adb (full path if adb is not in PATH).
```powershell
$env:JAVA_HOME = "C:\Program Files\Android\Android Studio\jbr"
cd "src\mobile\android" # or full path, e.g. C:\...\XerahS\src\mobile\android
.\gradlew.bat assembleDebug
```
Install and optionally launch:
```powershell
$adb = "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe"
& $adb install -r "src\mobile\android\app\build\outputs\apk\debug\app-debug.apk"
& $adb shell monkey -p com.getsharex.xerahs.mobile -c android.intent.category.LAUNCHER 1
```
If the first build fails with JAVA_HOME or KeyboardOptions, apply the fixes in the Kotlin section above before retrying.
### MAUI (recommended: use script)
```powershell
.\build\android\build-and-deploy-android-maui.ps1
# If you need a clean first (e.g. after fixing locks):
.\build\android\build-and-deploy-android-maui.ps1 -Clean
```
- Script uses **`-m:1`** and runs clean (if `-Clean`), build, then adb install and launch.
- **Timeout**: Run with a **5-minute** cap. If the build does not finish in 5 minutes, stop and fix locks/processes.
### Avalonia
```powershell
.\build\android\build-and-deploy-android-ava.ps1
.\build\android\build-and-deploy-android-ava.ps1 -Clean
```
The Avalonia script **always** uses **`-m:1`** to avoid file locks. Ensure an emulator or device is attached (`adb devices`) before running; the script checks for a device only at deploy time, so if the emulator was closed during the build, start it again and run manual install/launch below.
### Manual install and launch (when APK already exists)
Use when the APK is already built but deploy failed (e.g. no device at script run time), or to re-launch without rebuilding. Set `$adb` from `ANDROID_HOME` or `%LOCALAPPDATA%\Android\Sdk` if needed.
**MAUI:**
```powershell
$adb = "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe"
& $adb install -r "src\mobile-experimental\XerahS.Mobile.Maui\bin\Debug\net10.0-android\com.getsharex.xerahs-Signed.apk"
& $adb shell monkey -p com.getsharex.xerahs -c android.intent.category.LAUNCHER 1
```
**Avalonia:**
```powershell
$apk = (Get-ChildItem "src\mobile-experimental\XerahS.Mobile.Ava\bin\Debug\net10.0-android\*.apk" | Select-Object -First 1).FullName
& $adb install -r $apk
& $adb shell monkey -p com.getsharex.xerahs -c android.intent.category.LAUNCHER 1
```
---
## Success criteria
- Build completes in **under 5 minutes** (typically 2–3 min with `-m:1` and no locks for .NET; Kotlin Gradle builds often 1–2 min once JAVA_HOME is set).
- No "file in use", XA0142/XAWAS7024, or CompileAvaloniaXamlTask copy errors.
- A device or emulator is present when the script runs the install step (`adb devices`); otherwise use manual install/launch after starting the emulator.
- **Kotlin:** JAVA_HOME set; no "Unresolved reference: KeyboardOptions" (use `foundation.text.KeyboardOptions` and add `compose.foundation` to the module); APK installs via `adb install -r` and app runs.
- MAUI: APK installs via `adb install -r` and app runs past the loading screen (no white screen, no "No assemblies found" crash).
- Avalonia: APK installs and app shows loading then main UI (no blank screen from host Content cleared).
---
## References
- `developers/lessons-learnt/android_avalonia_init_fix.md` — Avalonia host-Content bug, MAUI defer-init and white screen
- `build/android/README.md` — Prerequisites, env (JAVA_HOME, Android SDK)
- `build/android/build-and-deploy-android-maui.ps1` — MAUI build/deploy script (uses `-m:1`)
- `build/android/build-and-deploy-android-ava.ps1` — Avalonia build/deploy script (uses `-m:1`)
- `.ai/skills/build-android/SKILL.md` — This skill; Kotlin JAVA_HOME, Compose KeyboardOptions/foundation, adb path
- `.ai/skills/build-linux-binary/SKILL.md` — Same `/m:1` and "never run two builds" guidance for Linux