Skip to content

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 — point JAVA_HOME at a JDK 25 install to skip that.
  • Android SDKandroid-app/local.properties must 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 signingbuild.gradle.kts signs release with the upload key (Play App Signing) when RELEASE_* env vars are set; otherwise it falls back to the debug key so assembleRelease stays installable for local/E2E verification. See releasing.md.
  • 16 KB devicessqlcipher-android ships 16 KB-aligned native libs, so the app runs on 16 KB emulators (e.g. the ps16k image) 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.