# Building OlliteRT
## Table of Contents
- [Prerequisites](#prerequisites)
- [Quick Start](#quick-start)
- [Product Flavors](#product-flavors)
- [App Icons](#app-icons)
- [Versioning](#versioning)
- [Signing Release Builds](#signing-release-builds)
- [HuggingFace OAuth](#huggingface-oauth)
- [Lint & Tests](#lint--tests)
- [Model Allowlist](#model-allowlist)
- [R8 & ProGuard](#r8--proguard)
---
## Prerequisites
- **Android Studio** (latest stable) or the Android SDK command-line tools
- **JDK 21** — required by AGP 9.x. Android Studio bundles a compatible JBR. The bytecode target is Java 11.
- **Android SDK** — API level 36 (`compileSdk 36`), target SDK 35
- **Gradle** 9.4.1 (bundled via wrapper)
- **Git** — required at build time to embed the commit hash in `BuildConfig.GIT_HASH` and for auto-versioning (`APP_VERSION_CODE=auto`)
- **LiteRT LM SDK** — bundled via Gradle dependency (see [SDK Compatibility](SDK_COMPATIBILITY.md) for version mapping)
- **Minimum SDK** — Android 12 (API 31)
### `local.properties`
Android Studio creates this file automatically. If you're building from the command line without Android Studio, create `Android/src/local.properties` manually:
```properties
sdk.dir=/path/to/your/Android/Sdk
```
This file is gitignored — every developer sets their own path.
For the internal architecture, package structure, threading model, and request flow, see **[ARCHITECTURE.md](ARCHITECTURE.md)**.
## Quick Start
> [!IMPORTANT]
> These instructions use Linux/macOS shell syntax. On **Windows**, use `gradlew.bat` instead of `./gradlew`, and set environment variables with `set JAVA_HOME=...` (cmd) or `$env:JAVA_HOME = "..."` (PowerShell) instead of `export`.
```bash
cd Android/src
# Debug build (uses debug signing, no R8 minification)
./gradlew :app:assembleStableDebug
# Compile check only (fastest verification)
./gradlew :app:compileStableDebugKotlin
```
If your Java or Android SDK paths differ from the defaults, override them:
```bash
JAVA_HOME="/path/to/jbr" ANDROID_HOME="/path/to/sdk" ./gradlew :app:assembleStableDebug
```
### APK Output
After building, APKs are in:
```
Android/src/app/build/outputs/apk/{flavor}/{buildType}/
└── OlliteRT-{flavor}-{gitHash}-arm64-v8a-{buildType}.apk
# For the Quick Start command (assembleStableDebug):
# OlliteRT-stable-3b93b24-arm64-v8a-debug.apk
#
# Other examples:
# OlliteRT-stable-3b93b24-arm64-v8a-release.apk
# OlliteRT-beta-3b93b24-arm64-v8a-release.apk
# OlliteRT-dev-3b93b24-arm64-v8a-debug.apk
```
> [!NOTE]
> Only **arm64-v8a** is supported. The LiteRT native library crashes on x86_64 emulators (SIGILL — unsupported CPU instructions), and 32-bit architectures have no native libraries at all. Nearly all Android devices from 2017+ are arm64-v8a.
## Product Flavors
| Flavor | Application ID | Icon | Purpose |
|:-------|:---------------|:----:|:--------|
| `stable` | `com.ollitert.llm.server` |
| Stable release |
| `beta` | `com.ollitert.llm.server.beta` |
| Beta testing |
| `dev` | `com.ollitert.llm.server.dev` |
| Local development |
All three flavors can be installed side-by-side on the same device.
Build variants follow the pattern `{flavor}{Debug|Release}` — e.g. `stableDebug`, `betaRelease`.
## App Icons
Source icon files (1024x1024 PNG) are in `assets/Icons/`:
| File | Flavor | |
|:-----|:-------|:---:|
| `OlliteRT_Logo_Icon_Stable.png` | `stable` — blue hexagon |
|
| `OlliteRT_Logo_Icon_Beta.png` | `beta` — yellow hexagon + BETA badge |
|
| `OlliteRT_Logo_Icon_Dev.png` | `dev` — red hexagon + DEV badge |
|
Flavor-specific Android resources are in:
- `Android/src/app/src/main/res/` — stable (default)
- `Android/src/app/src/dev/res/` — dev overrides
- `Android/src/app/src/beta/res/` — beta overrides
See the icon generation table in the App Icon section of the project's internal design reference for sizes and pre-padding requirements.
## Versioning
Version is defined in `gradle.properties`:
```properties
APP_VERSION_NAME=0.8.0
APP_VERSION_CODE=auto
```
When `APP_VERSION_CODE=auto`, the version code is derived from `git rev-list --count HEAD` at build time. CI can override both values via Gradle project properties:
```bash
./gradlew :app:assembleStableRelease \
-PAPP_VERSION_CODE=42 \
-PAPP_VERSION_NAME=1.0.0
```
The short git commit hash is automatically captured at build time and available as `BuildConfig.GIT_HASH`.
## Signing Release Builds
Release builds **require** a signing keystore — they will fail without one. Debug builds are not affected and always use the debug keystore.
The build validates the signing config at configuration time:
- Missing or blank properties → warning with the specific missing fields
- Keystore file doesn't exist at the given path → warning with the path
- No signing config at all → release build fails at the signing step
> [!IMPORTANT]
> Use **forward slashes** in paths, even on Windows (`C:/Users/you/keystore.jks`). Java's `Properties.load()` treats backslashes as escape characters and silently strips them.
### Option A: Local `keystore.properties` file (recommended for local dev)
Create `Android/src/keystore.properties` (gitignored):
```properties
storeFile=/path/to/your-keystore.jks
storePassword=your-store-password
keyAlias=your-key-alias
keyPassword=your-key-password
```
All four fields are required. The `storeFile` path can be absolute or relative to the `app/` module directory.
### Option B: Environment variables (CI)
The GitHub Actions release workflow uses this method with repository secrets:
```bash
export KEYSTORE_FILE=/path/to/keystore.jks
export STORE_PASSWORD=...
export KEY_ALIAS=...
export KEY_PASSWORD=...
```
### Building
```bash
# Signed release build
./gradlew :app:assembleStableRelease
# Android App Bundle (for Play Store)
./gradlew :app:bundleStableRelease
```
> [!NOTE]
> If you see `WARNING: Release keystore not configured` during a debug build, this is informational only — debug builds are not affected. You only need the keystore for release variants.
## HuggingFace OAuth
> [!NOTE]
> This is **not required** for most users. Users can enter their own HuggingFace API token directly in the app's Settings screen to download models — no OAuth setup needed. OAuth is only necessary if you want to enable the "Sign in with HuggingFace" flow for accessing gated models.
To set up OAuth:
1. Create a [HuggingFace Developer Application](https://huggingface.co/docs/hub/oauth#creating-an-oauth-app)
2. In `Android/src/app/src/main/java/com/ollitert/llm/server/common/ProjectConfig.kt`, replace the `clientId` and `redirectUri` placeholders with your HF app values
3. In `Android/src/app/build.gradle.kts`, update `manifestPlaceholders["appAuthRedirectScheme"]` to match your redirect URL
## Lint & Tests
```bash
# Lint check (flavor-specific)
./gradlew :app:lintStableDebug
# Unit tests
./gradlew :app:testStableDebugUnitTest
# Both at once
./gradlew :app:compileStableDebugKotlin :app:lintStableDebug :app:testStableDebugUnitTest
```
## Model Allowlist
The model allowlist source of truth is:
```
model_allowlists/v1/model_allowlist.json ← edit this file
```
A Gradle `syncAllowlist` task (defined in `app/build.gradle.kts`) copies it to `Android/src/app/src/main/assets/model_allowlist.json` during `preBuild`. **Never edit the assets copy directly** — it will be overwritten on the next build.
See [MODEL_ALLOWLIST_SCHEMA.md](MODEL_ALLOWLIST_SCHEMA.md) for the full field reference.
## R8 & ProGuard
Release builds (`*Release` variants) are minified and shrunk with R8. ProGuard rules are in `Android/src/app/proguard-rules.pro` with keep rules for kotlinx.serialization, Kotlin Reflect, Ktor CIO, Protobuf Lite, Hilt/Dagger, LiteRT LM, AppAuth, and Compose.
If you add a new library that uses reflection or serialization, you may need to add ProGuard keep rules to that file — otherwise R8 will strip classes that are only accessed via reflection, causing runtime crashes in release builds only.