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.5.0, Kotlin 2.4.21 (root build.gradle.kts
lifts KGP above the AGP-embedded 2.2.10), KSP 2.3.12, Compose BOM
2026.09.00, Room 2.8.5, Hilt 2.60.1, SQLCipher 4.19.1. See
docs/architecture/tooling.md for the
full stack.
Build
cd android-app
./gradlew assembleDebug # debug APK (fast, no R8)
./gradlew assembleReleaseLocal # R8-minified, debug-key signed APK for local installs
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.
Workflow-only PRs run the Android gates: android-tests.yml no longer ignores
.github/**, so changing the test workflow validates the very jobs it
defines. Docs-only PRs (**.md, docs/**) and .opencode/**/.idea/**
remain deliberately skipped. docs-guard.yml runs
bash scripts/test-workflow-paths.sh on android-tests.yml edits; that
contract fails if the blanket exclusion returns or the required check names
(Unit Testing & Linting Android App, Instrumented Testing on Emulator)
change.
Common gotchas
- Release signing —
releaseis signed only with the upload key (RELEASE_*env vars) and fails without it; useassembleReleaseLocalfor a debug-signed minified build 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.