Getting Started (Developer)
Requirements
- JDK 17+ (Android Studio's bundled JBR works). Note: the Gradle daemon
is pinned to JVM 25 (
gradle-daemon-jvm.properties), so a machine with only JDK 17 will trigger an automatic foojay download of JDK 25 on first build — pointJAVA_HOMEat a JDK 25 install to skip that. - Android SDK —
android-app/local.propertiesmust point at it (sdk.dir=/path/to/Sdk). - Android Studio (recommended) or a command line with Gradle.
The project toolchain: AGP 9.3.1, Kotlin 2.3.10 (root build.gradle.kts
lifts KGP above the AGP-embedded 2.2.10), KSP 2.3.11, Compose BOM
2026.08.00, Room 2.8.4, Hilt 2.60.1, SQLCipher 4.18.0. See
docs/architecture/tooling.md for the
full stack.
Build
cd android-app
./gradlew assembleDebug # debug APK (fast, no R8)
./gradlew assembleRelease # R8-minified release APK (debug-key signed)
sqlcipher-android ships 16 KB-aligned native libraries, so no build-time
step is required — see ADR-0011.
Run on an emulator
# 1. Install the debug APK.
adb install app/build/outputs/apk/debug/app-debug.apk
# 2. Grant notification-listener access:
adb shell cmd notification allow_listener com.zharif.autobudget/.listener.TransactionNotificationListener
# 3. Complete onboarding in the UI (consent → notification access →
# location → bank selection).
# 4. From android-app/, inject fixture notifications:
scripts/post_fake_notifications.sh --broadcast # DEBUG builds only
scripts/post_fake_notifications.sh --duplicate --broadcast # proves dedupe
--broadcast feeds the fixtures through DebugFixtureReceiver straight into
the capture pipeline (API 35+ removed cmd notification post -p). Use
--shell/default on older APIs or real devices. Parseable fixtures appear in
Transactions exactly once; unparseable ones land in the review queue.
Tests
./gradlew testDebugUnitTest # pure-JVM unit tests (no device)
./gradlew connectedDebugAndroidTest # instrumented tests (device/emulator)
Unit tests are pure JVM — no Robolectric — so they're fast. See Testing for the inventory.
Git hooks
pre-commit runs the repo's fast checks locally so CI failures surface at commit time. Enable once per clone:
pip install pre-commit
pre-commit install
.pre-commit-config.yaml mirrors CI: file hygiene, markdownlint-cli2
(config: .markdownlint-cli2.jsonc), shellcheck, Gradle ktlint (same task +
baseline as CI) and the docs-guard contract. Heavy checks (lint, unit
tests) stay in CI. Skip a slow hook once with SKIP=ktlint git commit …;
run everything on demand with pre-commit run --all-files. .editorconfig
keeps editors aligned (ktlint reads it too — keep Kotlin settings at ktlint
defaults).
CI notes
The android-tests.yml / build-artifacts.yml workflows install JDK 25
(Temurin), matching android-app/gradle/gradle-daemon-jvm.properties
(toolchainVersion=25), so the Gradle daemon reuses the installed JDK — no
foojay auto-download. AGP 9 requires JDK 17+ to run; 25 is fine.
Compilation targets Java 11 (compileOptions).
SDK packages in CI match the repo: platforms;android-37.0 (AGP 9 stores
compileSdk 37 as 37.0), build-tools;36.0.0.
Common gotchas
- Release signing —
build.gradle.ktssigns release with the upload key (Play App Signing) whenRELEASE_*env vars are set; otherwise it falls back to the debug key soassembleReleasestays installable for local/E2E verification. See releasing.md. - 16 KB devices —
sqlcipher-androidships 16 KB-aligned native libs, so the app runs on 16 KB emulators (e.g. theps16kimage) with no compat property needed. - Room schema — exported to
app/schemas/(ksp room.schemaLocation); keep it committed, and read it when writing migrations. com.android.shell— tracked + has debug rules only in DEBUG builds; never in release.