Balanced is a Flutter mobile app for recording income and expenses, seeing your running balance, and reviewing transactions over time. The project package name is balance_sheet; the in-app product name is Balanced, with the tagline “…know where your money goes.”
- Home (main tab) — Shows total balance, today’s net (income minus expense for the current day), and a Recent transactions list. Add entries via Income and Expense actions; open All transactions for the full report view.
- Contacts — Manage people or entities you attach to transactions (optional linkage from the transaction form).
- Profile — Theme (light / dark / system), font family, app lock (PIN and optional biometrics), and backup export/import.
- Insights — Period summary (today vs yesterday, this week, this month, last month): total expenses vs the comparison window, category donut + horizontal category bars, weekly income vs expense grouped bars, daily net line chart, and short takeaways. Activity remains a placeholder; the shell and bottom navigation are in place.
Money is stored and calculated in minor units (integer cents/kobo-style amounts). Display formatting in lib/utils.dart uses Nigerian Naira (NGN) via intl; adjust there if you target another currency.
| Area | Choice |
|---|---|
| Framework | Flutter (Dart SDK >=3.0.0 <4.0.0) |
| State & navigation | GetX (GetMaterialApp, Obx, controllers, Get.to / Get.offAll) |
| Local database | sqflite — SQLite file on device |
| Key-value preferences | get_storage — PIN metadata, theme, font, fingerprint flag |
| UI | Material 3 (useMaterial3: true), google_fonts, modal_bottom_sheet, pinput, flutter_slidable, fl_chart (Insights) |
| Security | crypto (salted PIN hash), local_auth |
| Backup | JSON file via file_picker |
High-level layout of lib/:
lib/
├── main.dart # App entry: GetStorage init, global GetX controllers, GetMaterialApp + themes
├── enums.dart # TransactionType, ReportType
├── utils.dart # Currency / signed net formatting
├── file_handler.dart # Legacy / commented CSV helpers (not active)
│
├── constants/ # App-wide constants
│ ├── app.dart # Storage keys (PIN, theme, font)
│ ├── db.dart # DB name, table names, schema version
│ ├── category.dart # Category keys, labels, icons, pill colors (keys are stable across releases)
│ ├── colors.dart # Legacy snackbar / accent colors
│ └── backup_constants.dart # Backup JSON format id + version
│
├── theme/
│ ├── app_theme.dart # Light/dark ThemeData, Google Font wiring, scaled text theme (“midnight” scale)
│ └── app_palette.dart # AppPalette ThemeExtension — semantic colors for light/dark
│
├── controllers/ # GetX controllers (business + UI state)
│ ├── appController.dart # Tab index, splash → lock vs home, theme mode & font persistence
│ ├── transactionController.dart
│ ├── contactController.dart
│ ├── reportController.dart # Period filters, report list state (scoped to report screen lifecycle)
│ ├── insights_controller.dart # Insights tab aggregates and period bounds
│ └── securityController.dart
│
├── models/
│ ├── transaction.dart
│ └── contact.dart
│
├── database/
│ ├── db.dart # Singleton AppDb, openDatabase, onCreate / onUpgrade migrations
│ └── operations.dart # CRUD and queries used by controllers
│
├── security/
│ └── pin_hash.dart # Salted SHA-256 PIN; no plaintext PIN on disk
│
├── backup/
│ └── backup_service.dart # JSON export/import of DB rows + GetStorage preferences
│
├── dialogs/ # Modals for transaction actions, contacts
├── widgets/ # Reusable UI (inputs, nav, pin, category pills, transaction rows, grid painter)
├── screens/ # Full-screen routes (splash, home shell, tabs, lock, forms, report)
└── assets/ # Images (e.g. launcher / splash source)
Platform folders (android/, ios/) contain standard Flutter embedding; launcher icon and native splash are configured in pubspec.yaml (flutter_launcher_icons, flutter_native_splash).
- Controllers are registered once in
lib/main.dart:TransactionController,SecurityController,AppController,ContactController. - Reactive UI uses
Obx/.obswhere lists or settings must update without manualsetStatein parent widgets. - Default transition is
Transition.downToUp(GetX config) for a consistent sheet-like feel on pushes. ReportControlleris created withGet.putinsideReportViewand deleted indisposeso report-specific state does not leak globally.
- Transactions and contacts live in SQLite (
lib/database/db.dart). Schema version and migrations are inonUpgrade(e.g. version 3 reworked the transactions table and removed legacy tables). - Preferences and secrets metadata (not the raw PIN) live in GetStorage: theme mode, font id, fingerprint toggle, PIN hash + salt.
Splashis the initialhomeroute (branding + short delay).AppController.onReadyroutes toLockScreenif a PIN is configured, otherwiseHome.Homeis a 5-tab scaffold: Main, Contacts, Activity (placeholder), Insights, Profile.PopScopesends Android back to tab 0 when not on the first tab.
- New transactions open as a modal bottom sheet with a transparent background and a wrapped form (
showNewTransactionModal), usingmodal_bottom_sheetpatterns and scroll-friendly layout.
- Fields include:
description,type(income/expenditure),amount(integer minor units),date(epoch ms),category(string key), optionalcontactId. - The Dart model is
Transaction; JSON serialization matches backup and DB rows.
- Defined in
lib/constants/category.dart. Keys are treated as stable API once shipped (labels and styling may change). Icons and per-theme pill colors (CategoryPillStyle) keep lists scannable in both light and dark mode.
- Simple
id+nametable; transactions referencecontactIdwhere relevant.
- Semantic colors live in
AppPalette(lib/theme/app_palette.dart), registered as aThemeExtensiononThemeData. Screens resolve colors withAppPalette.of(context)so widgets stay theme-aware. - Dark default is a deep blue-gray background with mint (positive / income) and coral (expense / loss) accents. Light mode keeps the same role structure with adjusted contrast.
app_theme.dartapplies Google Fonts with a fixed type scale (midnightScaledTextTheme) so switching font family does not break hierarchy.- Users can pick among several sans and monospace families (
AppFontIds); the choice is persisted and applied when buildingThemeData.
MidnightGridPainter— Subtle grid lines behind scrollable content for depth without clutter.- Glass-style balance card — Backdrop blur, gradient, and accent tied to whether today’s net is positive or negative (
MainView). - Bottom navigation — Custom
MidnightBottomNavaligned with the same visual language. - Transaction rows — Built in
widgets.dartwithflutter_slidable: swipe for edit/delete, category pills fromCategories, and consistent typography.
Internal UX notes for the transaction list live under design/ (e.g. redesign markdown); treat them as product/design references, not runtime code.
- PIN — Four digits (
AppConstants.PIN_CODE_LENGTH). Stored as salt + SHA-256 hash (PinHash); plaintext PIN keys are cleared on upgrade path when setting a new PIN. - Biometrics — Optional;
local_authintegrates withSecurityControllerfor unlock when enabled. - Lock flows —
LockScreenfor unlock / PIN removal confirmation;PinLockfor setup and change flows.
BackupServiceexports a JSON document with:- Format id / version (
BackupConstants) - DB schema version
- Contacts and transactions
- Selected GetStorage keys (theme, font, fingerprint, PIN hash/salt — so restores preserve lock state if desired)
- Format id / version (
- Import replaces local DB content and preferences; controllers expose refresh paths after import (see backup service and
syncFromStorage-style methods onAppController/SecurityController).
- Flutter SDK compatible with the
environmentconstraint inpubspec.yaml. - Xcode (iOS) and/or Android Studio / SDK (Android) as usual for Flutter mobile builds.
cd balance_sheet
flutter pub get
flutter runflutter test- App version:
pubspec.yaml→version:(e.g.1.3.0+1). ProfileViewshows a_kAppVersionconstant — keep it in sync withpubspec.yamlwhen releasing.- Regenerate launcher icon / splash after asset changes:
dart run flutter_launcher_icons
dart run flutter_native_splash:create- Prefer small, focused changes; match existing naming, GetX patterns, and
AppPaletteusage rather than introducing parallel styling systems. - Do not rename category keys in
category.dartfor existing categories without a migration strategy — comments in that file call this out explicitly. - When changing the backup JSON shape, bump
BackupConstants.formatVersionand document the migration expectation for older files.
The package is marked publish_to: 'none' in pubspec.yaml (private app). Adjust if you later open-source or publish tooling around it.