Offline-first personal finance manager, encrypted at rest and with a home-screen widget.
FinFlow tracks income and expenses while working 100% offline. You log transactions by category, see your balance and trends in charts drawn with Canvas, and check the month's summary from a widget without opening the app. The local database is the single source of truth βthere's no backendβ and, since it's about money, it's encrypted at rest and protected with biometrics.
| π Real privacy | Your finances never leave the device. Local DB encrypted with SQLCipher; the passphrase is generated and sealed with Tink (AEAD) + Android Keystore. |
| β‘ Always works | Offline-first: zero loading screens, zero network errors. The UI reacts instantly thanks to Room + Flow. |
| π Clear picture | Monthly balance, spending by category (donut) and month-over-month trend (bars), all in Canvas for a lightweight APK. |
| π Present beyond the app | A Glance widget and WorkManager reminders so you never forget to log an expense. |
| π€ Your data is yours | One-tap CSV export via the Storage Access Framework β no storage permission, RFC 4180-escaped, spreadsheet-ready. |
In one sentence: keep control of your money in a way that's private, fast, and free of any internet or server dependency.
Vertical Slice Architecture (feature-first): each feature is a self-contained slice with its own domain / data / ui layers, so a change to charts never touches transactions. Inside every slice: MVI (one immutable UiState per screen), unidirectional data flow, and repository contracts owned by domain.
βββββββββββββββββββββββββββββββββ
β navigation Β· Navigation 3 β
βββββββββββββββββ¬ββββββββββββββββ
β
feature/* β one self-contained slice per feature ββββββββββββββββββββββββββ
β transactions Β· charts Β· security Β· settings Β· widget Β· reminders β
β β
β ββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ β
β β ui β ββββββΆ β domain β β
β β Compose + MVI β intentsβ use cases + contracts β β
β β ViewModel β /state β (pure Kotlin, no Android) β β
β ββββββββββββββββββββββββ ββββββββββββββββ²ββββββββββββββββ β
β β implements β
β ββββββββββββββββββββββββ β β
β β data β βββββββββββββββββββββββ β
β β repository impls β β
β ββββββββββββ¬ββββββββββββ β
ββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βΌ
ββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
β core/database β β core/ui Β· core/domain Β· core/di β
β Room + SQLCipher β β theme & shared UI Β· cross-feature β
β (Tink-sealed passphrase) β β contracts Β· Hilt wiring β
ββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ
| Layer | Technologies |
|---|---|
| Language & UI | Kotlin Β· Jetpack Compose (Material 3, brand palette β dynamic color deliberately off) Β· Navigation 3 Β· Canvas for charts (no charting libs) |
| Architecture | Vertical Slice (feature-first) with domain / data / ui per slice Β· MVI (one immutable state per screen) Β· Coroutines + Flow / StateFlow Β· Hilt |
| Data & security | Room (+ KSP) as the SSOT with Flow DAOs Β· SQLCipher Β· Tink (AEAD) + Android Keystore Β· androidx.biometric Β· DataStore |
| System | Jetpack Glance (widget) Β· WorkManager (reminders and deferred tasks) |
| Quality & tests | JUnit Β· Turbine Β· coroutines-test Β· hand-rolled fakes Β· Robolectric (UI flows on the JVM) Β· Roborazzi (screenshot goldens) Β· Kover (β₯95% coverage gate) Β· ktlint Β· detekt Β· Android Lint Β· SonarCloud Β· Gradle Build Scans |
Every push to main runs the full pipeline β and fails if any gate breaks:
- Static analysis β ktlint + detekt + Android Lint (
codeQuality). - Tests β unit + Robolectric UI flows (93 tests, one JVM run shared by all gates).
- Coverage gate β Kover fails the build under 95% line coverage on measured classes (domain, data, ViewModels; codegen and framework adapters excluded by design).
- Screenshot gate β Roborazzi diffs the charts against committed goldens; a single changed pixel without re-recording fails CI.
- SonarCloud β Quality Gate blocks the job (
sonar.qualitygate.wait), with a Build Scan published per run for diagnostics.
The goldens guarding the charts (light/dark, versioned in app/src/test/screenshots/):
| Decision | Why |
|---|---|
| No backend, ever | Privacy as a feature: financial data never leaves the device, so there is nothing to breach remotely. |
| SQLCipher + Tink-sealed passphrase | Encryption at rest for the whole DB; the passphrase is random, AEAD-encrypted and never stored in plaintext. |
| Canvas charts, no libraries | Two charts don't justify a dependency; custom drawing keeps the APK lean and the visuals on-brand. |
| Robolectric over emulator for UI flows | Seconds instead of minutes per run, same CI, and Roborazzi reuses the whole setup for screenshot tests. |
| R8 enabled on AGP 9.x | Release builds are shrunk and obfuscated using the new optimization DSL + keepRules source folder. |
| GitHub Actions pinned to SHA | Mutable tags (@v6) are a supply-chain vector; commits are immutable. Dependabot keeps the pins fresh. |
Requirements: JDK 17 (build; bytecode targets Java 11) Β· Android Studio with AGP
9.3Β·compileSdk 37Β·targetSdk 36Β·minSdk 26. All versions live in the Version Catalog (gradle/libs.versions.toml).
| Action | Command |
|---|---|
| Build (debug) | ./gradlew assembleDebug |
| Install on device | ./gradlew installDebug |
| Release build (R8) | ./gradlew assembleRelease |
| Format (ktlint) | ./gradlew ktlintFormat |
| Quality (lint + ktlint + detekt) | ./gradlew codeQuality |
| Format + verify everything β | ./gradlew formatAndAnalyze |
| Unit + UI-flow tests (JVM) | ./gradlew testDebugUnitTest |
| Coverage gate (β₯95%) | ./gradlew koverVerifyDebug |
| Screenshot gate | ./gradlew verifyRoborazziDebug |
| Re-record goldens (after an intentional visual change) | ./gradlew recordRoborazziDebug |
π‘ Before every commit:
./gradlew formatAndAnalyze.
FinFlow/
βββ app/src/main/java/com/hacybeyker/finflow/
β βββ core/
β β βββ database/ # Room + SQLCipher setup, Tink-sealed passphrase
β β βββ di/ # Hilt modules shared across slices
β β βββ domain/ # Cross-feature entities and contracts
β β βββ ui/ # Theme, design tokens, shared composables
β βββ feature/ # One vertical slice per feature, each with domain/ data/ ui/
β β βββ transactions/ Β· charts/ Β· security/ Β· settings/ Β· widget/ Β· reminders/
β βββ navigation/ # Navigation 3 graph
βββ app/src/test/ # Unit + Robolectric flows + Roborazzi goldens (screenshots/)
βββ config/detekt/ # detekt configuration
βββ gradle/libs.versions.toml # Version Catalog (dependencies SSOT)
βββ lint.xml Β· .editorconfig # Lint rules and code style
βββ CHANGELOG.md # Version history
βββ DESIGN.md # Design system (colors, typography, spacing, components)
βββ AGENTS.md # Standards and guide for AI assistants
The architecture, SOLID standards, MVI patterns and implementation rules live in AGENTS.md, and the visual design system (colors, typography, spacing, components, light/dark) in DESIGN.md β read both before touching any code (human or AI).
In short: ktlint android_studio style, max_line_length = 120, 4-space indentation, no wildcard imports or trailing commas, dependencies always via the Version Catalog, and UI built only from design tokens (MaterialTheme.*). Keep formatAndAnalyze and the tests green, and update the CHANGELOG.md.
Distributed under the MIT license β see LICENSE.
Copyright Β© 2026 Carlos Osorio (hacybeyker).



