Debt tracker Android app (Kotlin)
Find a file
Alan Gaudet 41d28d4c5a
Some checks are pending
CI / build (push) Waiting to run
docs: state §8's next milestone in the form the loop reads
The 3.x arc is live on Play and the next work is owner-directed. §8 said so in
prose only, which reads to the loop as a missing milestone rather than a
finished roadmap.

milestone-cycle v1.12.0 writes one canonical marker and re-parses it before
committing, so this file is now round-trip clean against the loop's own parser.
2026-07-25 13:49:42 -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 docs: trim Play release notes to ≤500 chars 2026-07-07 08:13:41 -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 feat(M15): copy, dates, and a11y honesty 2026-07-06 20:19:58 -04:00
docs docs: state §8's next milestone in the form the loop reads 2026-07-25 13:49:42 -04:00
gradle/wrapper Initial commit: Native Kotlin debt tracker app 2026-02-05 03:03:53 -05:00
store-assets chore(play): refresh store listing for 3.0 — icon, screenshots, feature graphic, copy 2026-06-14 21:04:22 -04:00
tools M0: pre-flight cleanup — typed nav back-stack, per-screen ViewModels, dialog relocation (+ circular icon) 2026-06-11 12:02:48 -04:00
.cgcignore release: v2.0.0 — visual redesign launch (teal-IOU icon + Play staged 5%) 2026-06-02 18:21:57 -04:00
.gitignore fix: DtPill contrast — use onPositiveContainer/onNegativeContainer tokens 2026-05-09 20:22:41 -04:00
AGENTS.md docs: make AGENTS.md the real context file, symlink CLAUDE.md and GEMINI.md 2026-07-25 03:08:54 -04:00
build.gradle.kts feat(ds): add Paparazzi screenshot tests for design system components 2026-05-09 13:21:59 -04:00
CHANGELOG.md release: cut v3.1.0 (versionCode 15) 2026-07-06 20:24:04 -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 docs: G18 addendum — pay-off amounts start empty (F5, 2026-07-06) 2026-07-06 18:15:04 -04:00
DESIGN-3.md feat: partial group pay-offs + who-owes-who Add debt control (F1, F3) 2026-06-21 21:51:32 -04:00
DESIGN.md M0: pre-flight cleanup — typed nav back-stack, per-screen ViewModels, dialog relocation (+ circular icon) 2026-06-11 12:02:48 -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 Initial commit: Native Kotlin debt tracker app 2026-02-05 03:03:53 -05:00
gradlew.bat Initial commit: Native Kotlin debt tracker app 2026-02-05 03:03:53 -05:00
LICENSE Add GPL-3.0 license 2026-02-27 08:39:43 -05:00
README.md docs: groups usability review → 3.1 arc plan (DESIGN-3.1, ROADMAP M11–M16) 2026-07-05 20:37:32 -04:00
ROADMAP.md roadmap: advance pointer to M16 2026-07-06 20:20:16 -04:00
settings.gradle.kts feat: add :design-system module with tokens, fonts, Paper theme (v2.0 Session 1) 2026-05-03 10:56:40 -04:00
TODOS.md M0: pre-flight cleanup — typed nav back-stack, per-screen ViewModels, dialog relocation (+ circular icon) 2026-06-11 12:02:48 -04:00

Debt Tracker

CI 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.0.1 live on Google Play (2026-06-21, 100% production) — three dogfood-driven fixes on top of 3.0: pay back group debts in any amount (partial / multiple payments), the Add-debt control asks who owes who, and the Groups screen shows a member count. 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)

Honest comparison including where Splitwise wins: vs Splitwise.

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
  • 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
  • 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 — 12 themes across Everyday, Mood, and Terminal groups (v2.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. Your financial information is never uploaded anywhere.

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: 35 (Android 15)

Building

Requirements

  • JDK 17 or 21
  • Android SDK 35

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 (CI / 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                       # 12-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/
│   └── (12 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/
│   ├── 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 × 12 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

Publishing to Google Play

The app uses Gradle Play Publisher for automated releases.

# Build and publish to production
./gradlew publishReleaseBundle

# Upload store listing only (description, release notes)
./gradlew publishListing
  • Service account: ~/.config/play-store/service-account.json
  • Track: production
  • Release notes: app/src/main/play/release-notes/en-US/default.txt
  • Store listing: app/src/main/play/listings/en-US/

Known Issues

Play Store edge-to-edge warnings: Google's static analysis flags setStatusBarColor, setNavigationBarColor, and LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES. These originate from AndroidX library bytecode (EdgeToEdgeApi26, EdgeToEdgeApi29 in activity-compose, SystemJobInfoConverter in work-runtime), not app code. The deprecated APIs provide backward compatibility for pre-Android 15 devices and cannot be removed by app developers. The app displays correctly edge-to-edge on Android 15. This is a known ecosystem issue.

License

GPL-3.0