Xpense Ledger
Local-first personal finance app for multi-account, multi-currency tracking. ViewModels own auth session and ledger logic, ProxyProvider keeps MainViewModel synced to the logged-in user, and a singleton SQLite service guards a single database connection with foreign keys enforced.
01. The Problem & Engineering Challenge
Most budgeting apps assume one user, one currency, and an always-online connection, with complex setup, ads, and slow sync on top. Xpense had to work the opposite way: offline-first on a single shared device, with multiple users isolated by userId, each holding several accounts across ISO 4217 currencies. Balances live as REAL values tied to UUID account keys, transactions carry income/expense types with ISO8601 dates and category icon codepoints, and every delete must cascade cleanly through users to accounts to transactions. On top of that ledger core, the app must convert all balances into one base currency with presisi FX math, cache rates for 24 hours to survive rate limits and dead zones, hash passwords with SHA-256 before they ever touch SQLite, generate premium PDF and CSV exports on demand, and aggregate thousands of categorized transactions into interactive pie stats without freezing the UI thread.
02. Architectural Solution
MVVM with Provider: models (Account, Transaction, User) use Equatable plus toMap/fromMap, views stay reactive through Consumer, and AuthViewModel plus MainViewModel split session from ledger logic. ProxyProvider injects auth into MainViewModel so every query stays scoped to the logged-in user. SqliteService is a singleton holding one connection to xpense.db with PRAGMA foreign_keys = ON.
Key Deliverables
- ›Multi-account ledger with income/expense CRUD, ISO8601 dates, and category icon codepoints
- ›Dashboard total-balance engine converting every account into the base currency
- ›Frankfurter FX pipeline with SharedPreferences TTL cache and isAutoUpdateEnabled toggle
- ›Premium PDF and CSV export shared via share_plus from path_provider temp files
- ›Interactive fl_chart pie stats grouped by category across daily, weekly, monthly, and yearly periods
03. Technical Decisions & Tradeoffs
Relational Schema v5 With Cascade Deletes
users, accounts, and transactions link through UUID keys and ON DELETE CASCADE, so removing a user or account cleans dependents without orphan rows.
24-Hour Manual TTL Rate Cache
Cached rates keyed per currency with timestamp validation cut bandwidth; cache hit under 24 hours, API miss after, and manual toggle for offline use.
SHA-256 Hashing Plus userId-Scoped Queries
Passwords hash with crypto before storage and login comparison, while every account query filters WHERE userId = ? for cross-user isolation.
Isolate Compute Plus RepaintBoundary Charts
Stat aggregation over 500+ transactions moves to a background isolate, PieTouchData expands the tapped sector, and RepaintBoundary keeps chart repaints isolated.