A native Android personal-finance app built with Jetpack Compose, Kotlin, MVVM + Clean Architecture, Coroutines & Flow, Room, Hilt, Retrofit, and WorkManager.
Harcam is a local-first finance companion: log expenses, organize them by category, set monthly and per-category budgets with calm 80% / over-budget signaling, watch live exchange rates, track recurring payments, and read real reports — all behind an optional PIN / biometric lock. The design follows a documented "Calm Money" system so the UI reads like a real finance product, not a toy.
Design language and architecture notes live in
docs/README.md.
![]() Dashboard |
![]() Reports |
![]() Budget |
![]() Expenses |
![]() Add Expense |
![]() Settings |
![]() Onboarding |
- Expenses — add / edit / delete, day-grouped ledger, category filter chips, swipe-to-delete with Undo.
- Categories — color + icon coded, seeded defaults, per-category month-to-date spend, "move to Other" safe delete.
- Budgets — monthly + per-category limits with calm escalation: NORMAL → WARNING (≥80%) → OVER (≥100%), always icon + text + numbers (never color alone).
- Reports — period tabs (Day/Week/Month/Year), stat cards (total / avg-per-day / top category / count), and Compose-native donut / bar / trend charts with accessible legends.
- Exchange rates — live TRY / USD / EUR via a REST API (Retrofit) behind a repository seam with a Room cache fallback and explicit Live / Cached / Error states.
- Recurring — subscriptions & regular bills with cadence + next-due; WorkManager materializes due occurrences into real expenses (idempotent) and reminds before budgets are crossed.
- Security — optional PIN + biometric app lock (EncryptedSharedPreferences +
androidx.biometric), re-locks on resume; amount masking option. - Settings — light / dark / system theme, currency, security & notification toggles, navigation shortcuts.
This project demonstrates a modern Android implementation for a personal-finance product. Each capability maps to real code:
| Requirement | Where it lives |
|---|---|
| Jetpack Compose | All UI in presentation/** + core/ui/components (custom charts, PinPad, finance components) |
| Kotlin | 100% Kotlin |
| MVVM / Clean Architecture | core / data / domain / presentation / di layering; repository seam |
| Android fundamentals | Single-activity, Navigation Compose, lifecycle-aware lock, notifications, permissions |
| REST APIs + Retrofit | data/remote (Retrofit + Moshi + OkHttp logging) → exchange-rate screen |
| Coroutines & Flow | StateFlow UiState, reactive Room Flow, combine / flatMapLatest / stateIn(WhileSubscribed) |
| Dependency Injection (Hilt) | di/** modules, @HiltViewModel, @HiltWorker factory |
| Background work | WorkManager workers (recurring materialization + budget reminders) |
| Local persistence | Room 2.6.1 with a real 1→2 migration; DataStore preferences |
| UI / UX | Documented "Calm Money" design system, four-state screens, accessibility (semantics, ≥48dp, reduced-motion), responsive ≥600dp |
| Testing | JUnit + Turbine + coroutines-test unit tests (UseCases, ViewModels, mappers) + Compose UI smoke tests |
| Git | Conventional history; docs-first workflow |
| Fintech interest | Budgets, categories, multi-currency, recurring payments, exchange rates |
MVVM + Clean Architecture with a repository seam — presentation and domain depend only on domain interfaces; data provides Room- and Retrofit-backed implementations.
flowchart TD
subgraph Presentation["Presentation (Jetpack Compose)"]
S[Screen / Composable]
VM["@HiltViewModel · StateFlow<UiState>"]
end
subgraph Domain["Domain (pure Kotlin)"]
UC[UseCases]
RI[Repository interfaces]
end
subgraph Data["Data"]
RImpl[Repository impl]
Room[(Room DB)]
Retro[Retrofit API]
end
W["WorkManager workers · recurring / reminders"]
S --> VM --> UC --> RI
RImpl -. implements .-> RI
RImpl --> Room
RImpl --> Retro
Retro -. cache fallback .-> Room
W --> UC
com.mustafakara.harcam
├── core/ # ui (theme tokens, components), navigation, util, common (AppResult, Clock)
├── data/ # local (Room: entity/dao/database), remote (Retrofit api/dto), mapper, repository
├── domain/ # model, repository interfaces, usecase
├── presentation/ # per-feature: <feature>UiState + @HiltViewModel + Screen (Compose)
├── di/ # Hilt modules (Database, Network, Repository)
└── work/ # WorkManager workers + scheduler + notifications
Key patterns
- State: immutable
UiStateper screen, exposed asStateFlow, combined from RoomFlows in UseCases viastateIn(WhileSubscribed(5s)). - Error contract: remote calls return a sealed
AppResult<T>/AppError;HttpException/IOExceptionnever leak pastdata. The exchange Room cache is the offline fallback. - Theme: Material 3
ColorScheme+ anAppColorsextension for finance tokens; no raw hex in composables — only semantic tokens.
See docs/README.md for the design language, layer map, flows, and testing notes.
| Area | Tech | Version |
|---|---|---|
| Language | Kotlin | 1.9.20 |
| UI | Jetpack Compose (BOM) | 2023.10.01 · Material 3 |
| Architecture | MVVM + Clean Architecture | — |
| DI | Hilt | 2.48 |
| Persistence | Room | 2.6.1 |
| Preferences | DataStore | — |
| Networking | Retrofit + Moshi + OkHttp | — |
| Background | WorkManager + hilt-work | 2.9.0 |
| Security | security-crypto + biometric | — |
| Async / reactive | Coroutines + Flow / StateFlow | 1.7.3 |
| Navigation | Navigation Compose | 2.7.6 |
| Testing | JUnit · Turbine · coroutines-test · MockK · compose-ui-test | — |
| SDK | minSdk 24 · targetSdk / compileSdk 34 | — |
# Build the debug APK
./gradlew :app:assembleDebug
# Run the unit test suite
./gradlew :app:testDebugUnitTest
# Run the Compose UI / instrumented tests (needs a device or emulator)
./gradlew :app:connectedDebugAndroidTestOr open the project in Android Studio and run the app configuration. The exchange-rate base URL is provided via BuildConfig.EXCHANGE_API_BASE_URL (a free, key-less endpoint).
Mustafa Kara
- GitHub: mustafa-kara
- LinkedIn: -mustafakara
- Email: mustafakara.dev@gmail.com






