An offline Android expense tracker on Google Play that shares a group as a file, with no account or server. https://alangaudet.dev/projects/debt-tracker
Find a file
Alan Gaudet 03393ca13b
Some checks failed
CI / build (push) Has been cancelled
docs: closeout, Cloudflare purge skipped
Alan skipped the purge after the token refused it. The old image URLs age out of the edge cache; no page links to them.
2026-09-22 20:00:23 -04:00
.forgejo/workflows ci: fix SIGPIPE exit 141 on sdkmanager license accept 2026-05-09 17:11:44 -04:00
.github/workflows ci: fix SIGPIPE exit 141 on sdkmanager license accept 2026-05-09 17:11:44 -04:00
app release: v3.4.3 (versionCode 24) 2026-09-16 14:24:11 -04:00
design docs: adopt 3.0 redesign — handoff bundle, M0–M10 roadmap, loop-ready session notes 2026-06-11 00:42:14 -04:00
design-system F25: system bars without enableEdgeToEdge(); Play's edge-to-edge items 2026-09-16 02:16:25 -04:00
docs docs: closeout, Cloudflare purge skipped 2026-09-22 20:00:23 -04:00
gradle/wrapper build: Gradle 9.7.1 / AGP 9.3.0 / Kotlin 2.4.10 / KSP 2.3.11 / Room 2.8.4 / Compose BOM 2026.06.01 / Paparazzi 2.0.0-alpha05 / GPP 4.1.1; ktfmt + ktlint join the gate 2026-08-24 21:43:02 -04:00
store-assets release: v3.4.0 (versionCode 21) — shared groups, live on Play at 100% 2026-08-31 22:26:37 -04:00
tools F25: system bars without enableEdgeToEdge(); Play's edge-to-edge items 2026-09-16 02:16:25 -04:00
website website: size the theme gallery phones to their column 2026-09-22 19:34:24 -04:00
.cgcignore release: v2.0.0 — visual redesign launch (teal-IOU icon + Play staged 5%) 2026-06-02 18:21:57 -04:00
.editorconfig style: whole-tree ktfmt 0.64 + ktlint 1.8.0 (3.2.0) 2026-08-24 21:17:13 -04:00
.git-blame-ignore-revs docs: record the 3.2.0 style squash — .git-blame-ignore-revs, TODOS, AGENTS 2026-08-24 21:17:13 -04:00
.gitignore F25: system bars without enableEdgeToEdge(); Play's edge-to-edge items 2026-09-16 02:16:25 -04:00
AGENTS.md docs: website deployed; redeploy syncs into the bind-mounted dist 2026-09-22 19:13:39 -04:00
build.gradle.kts license: add GPL-3.0 notices to every source file 2026-08-25 22:58:38 -04:00
CHANGELOG.md docs: Play Console 'For your next release' panel clear after vc24 2026-09-16 16:22:35 -04:00
CLAUDE.md docs: make AGENTS.md the real context file, symlink CLAUDE.md and GEMINI.md 2026-07-25 03:08:54 -04:00
DESIGN-3.1.md release: 3.2.0 (versionCode 19) — six themes, 34 currencies, toolchain, whole-tree style, polish — squash of release/3.2.0 2026-08-24 23:30:46 -04:00
DESIGN-3.4.md release: v3.4.0 (versionCode 21) — shared groups, live on Play at 100% 2026-08-31 22:26:37 -04:00
DESIGN-3.md F21-F23: spend track by sign, Totals and person chart pass 2026-09-04 16:33:11 -04:00
DESIGN.md feat: six themes — Nature, Slate, Wall Street, Financial Guru, Hackerman, Typewriter (18, six per group) 2026-08-24 21:55:51 -04:00
GEMINI.md docs: make AGENTS.md the real context file, symlink CLAUDE.md and GEMINI.md 2026-07-25 03:08:54 -04:00
gradle.properties ci: remove hardcoded org.gradle.java.home from gradle.properties 2026-04-14 10:54:51 -04:00
gradlew build: Gradle 9.7.1 / AGP 9.3.0 / Kotlin 2.4.10 / KSP 2.3.11 / Room 2.8.4 / Compose BOM 2026.06.01 / Paparazzi 2.0.0-alpha05 / GPP 4.1.1; ktfmt + ktlint join the gate 2026-08-24 21:43:02 -04:00
gradlew.bat build: Gradle 9.7.1 / AGP 9.3.0 / Kotlin 2.4.10 / KSP 2.3.11 / Room 2.8.4 / Compose BOM 2026.06.01 / Paparazzi 2.0.0-alpha05 / GPP 4.1.1; ktfmt + ktlint join the gate 2026-08-24 21:43:02 -04:00
LICENSE Add GPL-3.0 license 2026-02-27 08:39:43 -05:00
README.md docs: 3.4.3 live on Play at 100%; record the publish receipt 2026-09-16 14:27:58 -04:00
ROADMAP.md release: v3.4.0 (versionCode 21) — shared groups, live on Play at 100% 2026-08-31 22:26:37 -04:00
settings.gradle.kts license: add GPL-3.0 notices to every source file 2026-08-25 22:58:38 -04:00
TODOS.md docs: closeout, Cloudflare purge skipped 2026-09-22 20:00:23 -04:00

Debt Tracker

Google Play License: GPL-3.0

A native Android app for tracking shared expenses and debts between people. Built with Kotlin and Jetpack Compose.

Fully offline, no accounts, no ads, no tracking.

v3.4.3 (2026-09-16) — The app rotates: the portrait lock the Play Console flagged for tablets and foldables is gone, a turn keeps whatever you were typing, and the Settings screen no longer sits under the camera cutout in landscape. Live on Google Play at 100% since 2026-09-16 as versionCode 24. 1107 tests. See CHANGELOG.md.

v3.4.2 (2026-09-16) — A fix under the hood for the Play Console's edge-to-edge items: the app no longer calls the Android APIs Google deprecated for drawing behind the system bars, and the bar icons still follow the theme on every supported Android version. Nothing on screen changes. Was live on Google Play at 100% on 2026-09-16 as versionCode 23 until 3.4.3 replaced it the same day. 1104 tests. See CHANGELOG.md.

v3.4.1 (2026-09-04) — The pay-off screen's spend bars show each member's share in grey, what is still owed in red and what is owed back in green, on every theme. The six-month chart on Totals shows its scale, and the person balance chart draws a zero line. Live on Google Play at 100% since 2026-09-04 as versionCode 22. 1087 tests. See CHANGELOG.md.

v3.4.0 (2026-08-31) — Shared groups without a server: send a group as a .dtgroup.json file, everyone logs on their own phone, and the app merges it back in any order; a group you already have adopts the file instead of doubling up. The one Play update carrying both this and 3.3.0, live at 100%. Database 6 → 7, backups at format 4.0; the system's own Auto Backup is off so a reinstall starts clean, while your folder backups still work. 1049 tests. See CHANGELOG.md.

v3.3.0 (2026-08-30) — Balances open with what the group has spent so far: the total, the number of expenses, the average per person, and each member's share beside what they covered. The group screen links there with a "spent so far" line. Cut and tagged locally; shipped to Play folded into v3.4.0. 884 tests. See CHANGELOG.md.

v3.2.0 (2026-08-24) — live on Google Play at 100%: six new themes for 18 in all, six per group; 34 currencies with a search box, no decimals for yen, won and đồng, and the group currency picked from the same list; Undo confirms in the top snackbar; the amount keypad stops at ten whole digits; three fixes from the on-device pass (F13–F15). Under the hood: ktfmt + ktlint over the whole tree and in the gate, Gradle 9.7 / AGP 9.3 / Kotlin 2.4 / Paparazzi 2, JDK 21 to build. 858 tests. See CHANGELOG.md.

v3.1.3 live on Google Play (2026-08-24, 100% production) — recorded group payments can be edited or deleted (tap the row in the group's list); on a person's screen a tap opens Edit / Delete; the amount keypad, the balance chart and the split editor show the group or default currency. 1490 tests. See CHANGELOG.md.

v3.1.2 (2026-08-24) — a crash fix: entering an amount of $2,000,000 or more in Add debt / Add payment no longer closes the app. Built on v3.1.1, which targets Android 16 (API 36) for Play's 2026-08-31 requirement. See CHANGELOG.md.

v3.0.0 (2026-06-12) — full Splitwise feature parity, kept entirely local: groups with five split modes and multiple payers, fewest-payments settlement, categories, an activity feed, search, monthly totals, receipts, and comments — plus a four-tab shell and an entry-flow redesign from real user feedback. 1324 tests (unit, snapshot, accessibility). See docs/ROADMAP_3.0.md and DESIGN-3.md.

v2.0.0 — the visual foundation: 20-component design system, 12 curated themes, WCAG AA contrast, reduced motion support. See DESIGN.md.

Why this exists

Splitwise — long the default for splitting bills with friends — added a 3-5 expense/day cap to its free tier in late 2024, with a $40/year Pro tier to remove it. Debt Tracker is for people who want to do the same job without an account, without ads, without a daily limit, and without a paywalled feature list.

Splitwise (free) Debt Tracker
Daily expense limit 3-5/day Unlimited
Account required Yes No
Ads Yes Never
Pro tier $40/year $0 forever
Works offline No Yes
Open source No GPL-3.0
Cross-user cloud sync Yes No (by design)
Share a group across phones Via their servers A file you send, merged locally

Honest comparison including where Splitwise wins: vs Splitwise (the page source; the GitHub Pages copy went with the deleted repo).

Features

  • Track People — Add anyone you share expenses with
  • Log Transactions — Record who owes whom, with descriptions and dates
  • Automatic Balances — See at a glance who owes you and who you owe
  • Group totals — Balances show the group's total spend, the average per person, and each member's share beside what they covered
  • Shared Groups — Send a group as a .dtgroup.json file; everyone keeps their own copy and the app merges what each person adds, in any order, with no account and no server. A group you already have adopts the file instead of doubling up.
  • Recurring Charges — Set up automatic monthly expenses (rent, subscriptions, etc.) with immediate first fire, full editing (amount, description, type, frequency, next-due date), on-demand "charge now" trigger, and 12-month backfill limit
  • Transaction History — View, edit, or delete past transactions; recorded group payments can be edited or deleted from the group's list
  • Backup & Restore — Export/import data as JSON files
  • Share Backups — Send backup files via email, cloud storage, or messaging apps
  • Automatic Backups — Schedule daily, weekly, or monthly backups. Stored outside the app via SAF so they survive uninstall. Snapper-style retention (3 daily, 2 weekly, 1 monthly).
  • Backup Browser — Browse and restore from automatic backups with human-readable dates, retention badges, and entity counts
  • Colour Themes — 18 themes across Everyday, Mood, and Terminal groups (12 in v2.0, six more in v3.2.0). Theme choice persists across app restarts.

Privacy

All data stays on your device. No internet connection needed, no accounts required, no analytics or tracking. The app never uploads your financial information. Sharing a group is deliberate: the app writes a file and you choose how it travels, with no server in the middle.

Privacy Policy

Tech Stack

  • Language: Kotlin
  • UI: Jetpack Compose with Material3
  • Architecture: MVVM with ViewModel + Repository
  • Database: Room (SQLite) with atomic transactions
  • Async: Kotlin Coroutines & Flow
  • Serialization: Gson
  • Screenshot Testing: Paparazzi (:design-system module)
  • Min SDK: 26 (Android 8.0)
  • Target SDK: 36 (Android 16)

Building

Requirements

  • JDK 21 (Paparazzi 2 needs it; point org.gradle.java.home at it in ~/.gradle/gradle.properties if your default is older)
  • Android SDK platform 36 (both modules); the Gradle plugin installs build-tools on demand

Debug Build

./gradlew assembleDebug

APK output: app/build/outputs/apk/debug/app-debug.apk

Release Build

Requires keystore.properties in the project root with signing config.

./gradlew bundleRelease

AAB output: app/build/outputs/bundle/release/app-release.aab

Testing

# Unit tests
./gradlew test

# Record Paparazzi screenshot goldens (after visual changes)
./gradlew :design-system:recordPaparazziDebug

# Verify screenshots match goldens (pre-merge)
./gradlew :design-system:verifyPaparazziDebug

Golden images are committed at design-system/src/test/snapshots/images/. The verify task fails if any component renders differently from its golden — run recordPaparazziDebug to update after intentional visual changes.

Project Structure

design-system/src/main/java/com/debttracker/ds/
├── DtTheme.kt                        # Entry point composable
├── DtThemeId.kt                       # 18-theme enum + mode/group flags
├── ThemeResolver.kt                   # DtThemeId → ResolvedTheme (colours, typo, shape)
├── tokens/
│   ├── DtColors.kt                    # Semantic colour tokens
│   ├── DtMotion.kt                    # Animation timing tokens
│   ├── DtSpacing.kt                   # 4dp grid spacing scale
│   ├── DtShape.kt                     # Corner radius tokens (xs/sm/md/lg/full/pill)
│   └── DtTypography.kt               # Type scale + font families
├── themes/
│   ├── ThemePalette.kt                 # Shared palette type
│   └── (18 files)                     # One palette per theme (Paper★, Midnight, Sage, …)
├── components/
│   ├── DtButton.kt                    # 5 variants, 3 sizes, loading state
│   ├── DtIconButton.kt               # Icon-only button with danger toggle
│   ├── DtCard.kt                      # Elevated card with hero variant
│   ├── DtListRow.kt                   # M3 ListItem wrapper
│   ├── DtTopBar.kt                    # Top app bar with scroll behaviour
│   ├── DtFab.kt                       # FAB + extended FAB
│   ├── DtAvatar.kt                    # Deterministic colour avatar
│   ├── DtPill.kt                      # 4-intent status pill
│   ├── DtBadge.kt                     # Compact inline badge
│   ├── DtTextField.kt                 # Outlined text input with error slot
│   ├── DtAmountField.kt              # Hero numeric input (no system IME)
│   ├── DtNumericKeypad.kt            # 4×3 keypad with TalkBack labels
│   ├── DtDateField.kt                 # Tap-to-open M3 DatePicker + presets
│   ├── DtChip.kt                      # Selectable filter chip (token-aware)
│   ├── DtConfetti.kt                  # Canvas-based celebration particles
│   ├── DtDialog.kt                    # M3 dialog wrapper + DtAction
│   ├── DtBottomSheet.kt               # M3 ModalBottomSheet wrapper
│   ├── DtEmojiChip.kt                 # 28dp emoji circle (transaction type icons)
│   ├── DtEmptyState.kt                # Centred no-data state with CTA
│   ├── DtSkeleton.kt                  # Rounded-rect shimmer placeholder
│   └── DtSnackbar.kt                  # Undo-capable snackbar with accent strip
├── util/
│   ├── DtCurrencies.kt                # 34-currency catalog: prefix, code, name, minor digits, words
│   ├── DtFormatAmount.kt              # Single-source currency formatter
│   ├── DtFormatAmountWords.kt         # TalkBack-friendly amount-to-words
│   └── DtReducedMotion.kt             # System reduced motion detection
└── preview/
    ├── DtThemePreview.kt              # Token showcase preview
    └── ComponentPreviews.kt           # All components × 18 themes

app/src/main/java/com/debttracker/app/
├── MainActivity.kt
├── DebtTrackerApplication.kt           # Application: owns the singleton DB + repository
├── data/
│   ├── backup/
│   │   ├── AutoBackupManager.kt       # Backup scheduling & preferences
│   │   ├── AutoBackupWorker.kt        # WorkManager background worker
│   │   └── BackupRetention.kt        # Snapper-style retention algorithm
│   ├── database/
│   │   ├── DebtTrackerDao.kt          # Room DAOs
│   │   └── DebtTrackerDatabase.kt     # Room database
│   ├── model/
│   │   ├── Person.kt                  # Person entity
│   │   ├── Transaction.kt             # Transaction entity
│   │   ├── RecurringCharge.kt         # Recurring charge entity
│   │   ├── PersonLastActivity.kt      # DAO query result for recency grouping
│   │   └── BackupData.kt              # Backup/restore data class
│   └── repository/
│       └── DebtRepository.kt          # Repository layer
└── ui/
    ├── AppStartupViewModel.kt         # Launch-time recurring processing + backup
    ├── HomeViewModel.kt               # Home: persons, recency, search, sticky pre-fill
    ├── PersonViewModel.kt             # Person detail (keyed per personId)
    ├── RecurringViewModel.kt          # Recurring charges (keyed per personId)
    ├── SettingsViewModel.kt           # Backup export / restore / clear-all
    ├── BackupBrowserViewModel.kt      # SAF backup-folder browse + restore
    ├── SnackbarEvent.kt               # Undo-carrying snackbar events
    ├── navigation/
    │   └── AppNavState.kt             # Typed back-stack holder (replaced enum Screen)
    ├── components/
    │   └── PersonPicker.kt            # Domain-coupled avatar row + "Add new"
    └── screens/
        ├── AddExpenseSheet.kt         # v2.0: bottom sheet with amount, person, type, date
        ├── BackupBrowserScreen.kt     # Browse & restore backups
        ├── Dialogs.kt
        ├── HomeScreen.kt              # v2.0: hero card, recency groups, search, FAB
        ├── HomeScreenHelpers.kt       # Pure functions for balance/recency/search logic
        ├── TransactionHistoryScreen.kt # v2.0: person header, sparkline, time-grouped txns
        ├── PersonScreenHelpers.kt     # Pure functions for time grouping, sparkline, labels
        ├── RecurringChargesScreen.kt
        ├── SettingsScreen.kt
        └── SettleUpDialog.kt          # v2.0: settle-up confirmation with formatted balance

License

GPL-3.0