← Back to projects

I was tracking debts between friends with a notes app and a calculator. That got old fast. I wanted something that handled the ledger, processed recurring charges automatically, and kept my data on my phone instead of some company's server. So I built it, and once it worked I put it on the Play Store.

A real Android app that people can install and use. Not a demo, not a prototype. Published on Google Play with release signing, ProGuard, automatic backups, and ten colour themes because I use it every day and dark mode matters. Offline-only by design.

MVVM + Repository single-activity · Jetpack Compose · Room COMPOSE UI Home History Settings Recurring Backup Browser StateFlow VIEWMODEL persons · selectedPerson · toastMessage: SharedFlow REPOSITORY @Transaction atomic · coroutines WORKMANAGER AutoBackupWorker · SAF storage ROOM DB PersonDAO · TransactionDAO · RecurringChargeDAO 3 daily · 2 weekly · 1 monthly
01

Room with atomic transactions

Every balance update and its matching transaction insert run inside a single Room @Transaction block. If the insert throws, the balance does not move. The app tracks money between people; partial writes are not acceptable.

02

Snapper-style backup retention

Automatic backups run on launch and via WorkManager on a daily schedule. Retention follows the Snapper model: keep 3 daily, 2 weekly, 1 monthly, prune the rest. Six files max, written to SAF storage outside the app sandbox so they survive an uninstall. No cloud account required.

03

Recurring charges with backfill

Recurring charges fire immediately on creation. If the app has not been opened in weeks, startup catches up: a backfill loop walks forward from the last due date, creating one transaction per missed period, up to a 12-month ceiling. Past that it fast-forwards to avoid flooding the ledger with years of backlog.

04

Single activity, ten themes

One Activity, enum-based screen routing, no Navigation component. Ten Material 3 colour themes stored in SharedPreferences and applied at the composition root. Edge-to-edge layout on Android 15+. The entire navigation fits in one composable function.

// play store publishing

Play Store publishing

Release signing, ProGuard, version management, store listing metadata. There's a real gap between 'runs on the emulator' and 'published and installable,' and working through it once means the second time is straightforward.

// offline-first as the feature

Offline-first as the feature

No accounts, no sync, no analytics, no internet. The app works on a phone in airplane mode. For a personal finance tool, keeping data on the device isn't a limitation — it's the whole point.

// room to grow

Room to grow

Multi-device sync would need a server or peer-to-peer layer on top of the existing Room schema. Categories, currency conversion, and balance-over-time charts all fit into the current ViewModel and Repository pattern without restructuring.

KotlinJetpack ComposeMaterial 3RoomWorkManagerCoroutines
// the code

Read the source, run it locally, open issues.