DEV Community

Asiv
Asiv

Posted on

Building a Zero-Telemetry Android Vault: Flutter, Encrypted SQLite, and Why We Ditched Cloud Sync

When building personal finance software, standard engineering advice says: 

"Spin up Supabase, plug in Plaid for automatic bank scraping, and charge $12/month."

We rejected that premise entirely when architecting MyneWallet.

Handling money data requires extreme operational discipline. If your remote server stores a user's transaction ledger, you are one zero-day exploit, misconfigured S3 bucket, or sub-processor breach away from exposing their entire financial identity.

Here is the engineering breakdown of how we built an offline-first, zero-telemetry financial vault in Flutter—handling decimal currency math without floating-point errors, deterministic envelope allocation, and client-side web verification.

  • The Core Constraint: Zero Network Permissions

The cleanest way to guarantee data privacy is architectural, not legal:

No Firebase SDKs.

No Mixpanel, Segment, or analytics beacons.

No network requests carrying payload figures.

Every ledger write, balance reconciliation, and category balance calculation runs strictly inside an on-device SQLite database encrypted with SQLCipher.

[User Device] 
   └── Flutter UI Layer 
          └── Integer Math Engine 
                 └── Encrypted SQLite Database (On-Device Storage Only)
The app operates identically in airplane mode because it has zero remote dependencies.

  • Handling Currency Math Without Floating-Point Traps

In financial software, standard IEEE 754 floating-point operations (0.1 + 0.2 = 0.30000000000000004) are unacceptable. Over multi-year ledgers with thousands of line items, floating-point rounding errors compound into balance drift.

To guarantee precision, all monetary values in our SQLite database are stored as 64-bit Integers representing minor units (cents):

Dart
class Money {
  final int minorUnits; // $14.99 is stored as 1499 integer cents

  const Money(this.minorUnits);

  Money operator +(Money other) => Money(minorUnits + other.minorUnits);
  Money operator -(Money other) => Money(minorUnits - other.minorUnits);

  String toFormattedString(String currencySymbol) {
    final double value = minorUnits / 100.0;
    return '(currencySymbol){value.toStringAsFixed(2)}';
  }
}
By enforcing integer arithmetic, our envelope auto-allocation algorithms never lose a single cent to binary rounding residue.

  • The Envelope Allocation Engine

Under a True Zero-Based Budgeting (ZBB) model, every single unit of income must be assigned a job before the month begins:

💡 The Core Equation:

To Assign = Unallocated Cash − Total Assigned Envelopes

When income arrives, our deterministic Auto-Assign engine allocates funds through a three-stage priority pipeline:

Fixed Monthly Obligations: Rent, utilities, debt minimums.

Sinking Fund Targets: Amortized monthly allocations for annual commitments.

Flexible Spending: Groceries, personal allowances, dining.

If a category overspends, the ledger flags the exact variance and prompts the user to reallocate from another envelope, keeping the core ledger balanced to zero.

  • Client-Side Web Companion & Open Methodology

Alongside the mobile app, we built 10 standalone client-side financial calculators that run in the browser without cookies, tracking scripts, or server requests:

Web Suite: https://thebrinklabs.com/tools/

Formulas & Verification Gates: https://thebrinklabs.com/methodology

Every formula, rounding rule, and mathematical test case is published openly, allowing users to verify our math directly in their browser's Network tab before downloading the mobile app.

Conclusion & Open Discussion
Privacy in software is often treated as a marketing bullet point. In practice, it is a set of hard architectural trade-offs: no password reset flows, no remote sync fallbacks, and total reliance on robust local database integrity.

Google Play: https://play.google.com/store/apps/details?id=com.thebrinklabs.mynewallet.expensetracker

Architecture Docs: https://thebrinklabs.com/

How are other indie developers handling local encrypted storage and offline state management in Flutter? Let's discuss in the comments below!

Top comments (0)