Skip to content

Repository files navigation

πŸ’Έ FinFlow

Your finances, in your pocket. No cloud, no accounts, no excuses.

Offline-first personal finance manager, encrypted at rest and with a home-screen widget.

Kotlin Jetpack Compose Material 3 Min SDK 26 Offline-First Encrypted CI Quality Gate Status Build Scan License MIT


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.

✨ What sets it apart

πŸ”’ 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.

πŸ›οΈ Architecture

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             β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ› οΈ Stack

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

πŸ§ͺ Quality gates

Every push to main runs the full pipeline β€” and fails if any gate breaks:

  1. Static analysis β€” ktlint + detekt + Android Lint (codeQuality).
  2. Tests β€” unit + Robolectric UI flows (93 tests, one JVM run shared by all gates).
  3. Coverage gate β€” Kover fails the build under 95% line coverage on measured classes (domain, data, ViewModels; codegen and framework adapters excluded by design).
  4. Screenshot gate β€” Roborazzi diffs the charts against committed goldens; a single changed pixel without re-recording fails CI.
  5. 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/):

Spending donut β€” lightΒ Spending donut β€” dark

Monthly bars β€” lightΒ Monthly bars β€” dark

🧭 Key decisions

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.

⚑ Commands

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.

πŸ—‚οΈ Structure

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

🀝 Contributing Β· πŸ€– 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.

πŸ“„ License

Distributed under the MIT license β€” see LICENSE.

Copyright Β© 2026 Carlos Osorio (hacybeyker).

Releases

Packages

Contributors

Languages