Building an AI-Native iOS Budgeting App in Two Weeks
Architecture and implementation lessons from an AI-assisted iOS budgeting app: Flutter, Firebase, Gmail events, category suggestions, precise money handling and review.
This article traces a two-week AI-assisted development effort for an iOS budgeting app. It focuses on the architecture and implementation lessons; a short development timeline does not establish production readiness.
The design connects Gmail change notifications, transaction parsing, Claude category suggestions, Firestore and mobile notifications. The examples below are instructional sketches and omit application-specific implementation; deployment, security and financial correctness require separate validation.
This isn't about hype. It's about what's actually possible in 2025 when you combine modern serverless infrastructure, AI-native development tools, and intelligent product design. The future of software development isn't just AI-assisted—it's AI-native, from the code we write to the features we ship.
FinHub is the budgeting-app example used here to explore that design.
The Vision: What Makes FinHub Different
Most budgeting apps suck. They require manual transaction entry, don't sync with all your banks, use clunky categorization rules, and feel like homework. The product goal was software that just worked—automatically, intelligently, instantly.
The product requirements:
- Manual expense tracking is tedious and error-prone
- Traditional budgeting apps require constant data entry
- Bank apps don't provide multi-account aggregation
- Generic categorization doesn't match user spending patterns
- International banking isn't well-supported
The AI-native solution I built:
Transaction extraction: an authorized mailbox event can trigger parsing and a proposed transaction. Unsupported formats and ambiguous amounts need a visible review path.
Category suggestions: Claude receives selected transaction context and prior corrections. This is contextual reuse, not evidence that the underlying model is retrained.
Asynchronous processing: Gmail notifications can trigger Cloud Functions after mailbox changes. End-to-end latency varies across the bank, Gmail, Pub/Sub, model, database and device.
Multi-currency support: the example uses COP and USD with exchange rates refreshed daily. Store the rate, source and effective time used for each conversion.
Correction reuse: reviewed category changes can become examples for future prompts. Measure whether they improve categorization; each correction does not guarantee a better result.
Dark Cyberpunk UI: Minimalist, fast, focused.
The design uses AI for development and category suggestions, with serverless components for hosting. Error handling, credential isolation, correct arithmetic, tested rules and recovery behavior remain explicit engineering responsibilities.
The Tech Stack: Architecture Deep Dive
Frontend: Flutter + Dart
Framework: Flutter 3.32.7
Language: Dart 3.8.1
Platform: iOS (iPhone)
UI: Material Design 3 with custom dark theme
State Management: Provider + Firestore streams
Navigation: go_router with auth guards
Why Flutter? Single codebase for beautiful native iOS UI. Hot reload means instant feedback during development. Excellent Firebase integration out of the box. Strong type safety with Dart catches bugs at compile time. Native performance without the complexity of Swift/SwiftUI.
Backend: Firebase Serverless Architecture
Authentication: Google Sign-In (OAuth 2.0, Gmail API scopes)
Database: Cloud Firestore (real-time NoSQL)
Compute: Cloud Functions (Node.js 20, TypeScript)
Messaging: Firebase Cloud Messaging + APNS
Firestore Collections:
- users — client-readable profile fields only, with explicitly authorized access.
- serverCredentials — OAuth tokens stored separately and denied to client reads; backend access restricted by IAM.
- pendingCandidates — deduplicated extracted proposals and review state; ingestion does not change balances. transactions — confirmed monetary records with currency, integer minor units, provenance and a stable posting key.
- wallets — account records and balances using the same money representation.
- categories — available classification choices.
- keywordMappings — reviewed examples used as context, not model training.
- emailLogs — processing audit records with an appropriate retention policy.
- notifications — delivery intents and in-app notification history.
Access design to implement and verify:
- Enforce user-scoped access to client data and protect balance updates.
- Keep OAuth credentials in server-only storage separate from readable profile documents. Firestore rules cannot hide individual fields in a readable document.
- Test client rules and backend IAM separately; server SDK authorization is a separate boundary.
Composite Indexes:
transactions(userId, timestamp DESC)- Main transaction listtransactions(userId, category, timestamp DESC)- Category filteringtransactions(userId, walletId, timestamp DESC)- Wallet filtering
Cloud Functions Architecture (TypeScript)
functions/
├── src/
│ ├── index.ts # Function entry points
│ ├── ai/
│ │ └── claude-service.ts # Claude AI integration
│ ├── gmail/
│ │ ├── auth-service.ts # OAuth token management
│ │ ├── gmail-service.ts # Gmail API client
│ │ └── watch-service.ts # Pub/Sub watch setup
│ ├── transactions/
│ │ └── transaction-builder.ts # Email → Transaction parser
│ └── notifications/
│ ├── push-notification-service.ts # FCM
│ └── in-app-notification-service.ts # Firestore
Key Cloud Functions:
processGmailNotification (Pub/Sub trigger)
- Triggered by Gmail Pub/Sub watch webhook
- Fetches new emails from Gmail API
- Identifies bank source (Bank A/Bank B/Payment Provider)
- Parses transaction details (amount, merchant, date)
- Calls Claude AI for categorization
- Durably stores a deduplicated pending candidate and a review-notification outbox entry; no ledger or balance update yet.
- Sends push notification to iPhone
triggerTokenRefresh (Scheduled, hourly)
- Ensures Gmail API access tokens stay fresh
- Rotates OAuth tokens automatically
- Re-establishes Pub/Sub watch if expired
updateExchangeRate (Scheduled, daily)
- Fetches COP/USD exchange rate
- Updates Firestore for multi-currency calculations
- Sends notification if rate changes significantly
storeAccessToken (Callable)
- Called from Flutter app after Google Sign-In
- Stores OAuth credentials in server-only storage after validating the authenticated user and authorization result.
- Sets up Gmail Pub/Sub watch
AI Integration: Claude Console API
interface ClaudeResponse {
type: "TRANSACTIONAL" | "INFORMATIONAL" | "UNKNOWN";
category?: string; // Emoji (🏠, 🥩, 🚕)
categoryDescription?: string; // "Rent", "Groceries", "Transport"
merchant?: string;
amountDecimal?: string; // Exact decimal text, e.g. "10.25"
currency?: string; // Validated currency code, e.g. "USD"
confidence?: number; // 0-100
message?: string;
}
// Model output is untrusted. Validate decimal syntax, currency and range.
// Parse exactly into currency-specific integer minor units; reject excess
// precision instead of converting through a binary floating-point number.
The AI Learning Loop:
- Email arrives → Claude analyzes content
- AI categorizes based on:
- Last 20 transactions (context)
- Last 50 user corrections (learning data)
- All available categories
- Email sender (Bank A/Bank B/Payment Provider)
- User reviews categorization
- If user changes category → stored in
keywordMappings - Future prompts may reuse reviewed corrections; evaluate accuracy rather than assuming improvement.
Why evaluate a model for categorization:
- Compare a model with rules on representative transaction descriptions.
- Use selected reviewed examples as prompt context.
- Evaluate supported languages and changed email formats.
- Validate structured responses and amounts before writing records.
- Estimate cost from workload, model usage and current public provider prices; no private account spending is reported.
The Real Innovation: Gmail Pub/Sub Architecture
Push notifications can avoid repeated empty mailbox polls. They signal that changes are available; reliable synchronization still requires saved cursors, recovery and periodic reconciliation of missed notifications.
The Pub/Sub solution:
graph LR
A[Bank Email Sent] --> B[Gmail Receives]
B --> C[Gmail Pub/Sub Watch]
C --> D[Cloud Pub/Sub Topic]
D --> E[processGmailNotification]
E --> F[Gmail API Fetch]
F --> G[Claude AI Categorization]
G --> H[Persist Pending Candidate and Outbox]
H --> I[FCM Push Notification]
I --> J[iPhone Lock Screen]
style A fill:#f7768e
style C fill:#7dcfff
style E fill:#e0af68
style G fill:#bb9af7
style I fill:#7aa2f7
style J fill:#bb9af7
How Gmail Pub/Sub Watch Works:
1. Setup Phase (after user signs in):
await gmail.users.watch({
userId: 'me',
requestBody: {
topicName: 'projects/example-project/topics/gmail-notifications',
labelIds: ['INBOX']
}
});
2. Email Arrives:
- An authorized mailbox receives a transaction-related email.
- Gmail publishes a change signal to Pub/Sub.
- The worker synchronizes history asynchronously, with retries and missed-event recovery.
3. Cloud Function Triggered:
export const processGmailNotification = onMessagePublished(
{ topic: "gmail-notifications" },
async (event) => {
const historyId = event.data.message.json.historyId;
// Fetch new emails since last history ID
// Process each email...
}
);
4. Email Processing Pipeline:
- Identify sender (Bank A/Bank B/Payment Provider filters)
- Extract transaction data (regex parsing + Claude AI)
- Determine wallet (based on email source)
- Persist a deduplicated pending candidate and review-notification outbox atomically.
- Leave the ledger and wallet balance unchanged until explicit confirmation.
- Deliver the review notification from the outbox; on confirmation, post the transaction and balance change once.
5. Push Notification to iPhone:
// Illustrative visible alert; no financial details on the lock screen.
await admin.messaging().send({
token: userFCMToken,
notification: {
title: "Transaction ready for review",
body: "Open the app to review the details."
},
apns: {
headers: { "apns-push-type": "alert", "apns-priority": "10" },
payload: { aps: { sound: "default", badge: 1 } }
}
});Token Management Challenge:
- Gmail Pub/Sub watch expires after 7 days
- OAuth access tokens expire after ~1 hour
- Solution: Scheduled Cloud Function runs hourly to refresh
Operational behavior to measure:
- Measure end-to-end latency and its upper percentiles.
- Renew Gmail watches and reconcile missed notifications.
- Handle retries and duplicate deliveries idempotently.
- Treat notification display and background execution as conditional device behavior.
The AI Development Process: Building with Claude Code
This is where things get meta. I used AI to build an AI-powered app.
The AI Trifecta:
- Claude Code - Primary development AI (autonomous coding)
- Cursor - AI-powered code editor (context-aware completions)
- Cursor Bug Bot - Automated PR review (catches bugs before merge)
How Claude Code Accelerated Development
Day 1-2: Project Setup & Architecture
Me: "Start mvp"
Claude Code:
- Loads PRD from .claude/prd/mvp/
- Presents 8-phase development plan
- Asks for confirmation
Me: "Proceed"
Claude Code:
- Creates Flutter project
- Configures Firebase
- Sets up folder structure
- Deploys security rules
- Commits + pushes to GitHub
No boilerplate hell. No "let me Google how to set up Firebase." Just describe what you want, confirm the plan, and Claude Code executes.
Day 3-5: Data Models & Backend Services
- Claude Code wrote 7 Firestore data models with JSON serialization
- Generated CRUD services for each collection
- Created default data initialization logic
- All with proper error handling, logging, and type safety
- Human input: Reviewed architecture decisions, adjusted business logic
Day 6-8: Gmail API Integration This was the most complex part: OAuth flows, token management, Pub/Sub webhooks.
Claude Code:
- Researched Gmail API documentation
- Implemented OAuth 2.0 flow with refresh token handling
- Set up Pub/Sub watch with automatic renewal
- Created email parsing logic for Bank A/Bank B/Payment Provider
- Built transaction extraction pipeline
Human oversight: Review representative authorized fixtures, refine parsing and test edge cases.
Day 9-10: Claude AI Integration Claude Code integrated its own API (yes, the AI coded itself into the product).
- Designed learning loop architecture
- Implemented category suggestion logic
- Built feedback collection system
- Human decision: Evaluate the selected model against alternative approaches on the intended workload.
Day 11-12: UI/UX Implementation
- Dark cyberpunk theme with custom color palette
- Transaction list with real-time Firestore streams
- Wallet management screens
- Category selection with emoji support
- Reports and analytics
- Human touch: Final design tweaks, animation timing
Day 13-14: Testing, Bug Fixes, Push Notifications
- Tests for documented acceptance criteria.
- Fixed edge cases (floating-point precision, multi-currency)
- Implemented FCM + APNS for background notifications
- Deployment requires its own operational acceptance checks.
The Claude Code Workflow Pattern
# 1. Start a session
start mvp
# 2. Claude Code:
# - Pulls latest from GitHub
# - Loads PRD context
# - Identifies next task
# - Presents plan
# - WAITS for confirmation
# 3. Developer confirms or adds requirements
proceed # or "add error handling" or "use different approach"
# 4. Claude Code:
# - Implements the feature
# - Writes tests
# - Commits changes
# - Updates documentation
# - Pushes to GitHub
# - ALL AUTOMATICALLY
# 5. Cursor Bug Bot reviews PR
# - Identifies issues
# - Suggests improvements
# - Claude Code fixes them
What Claude Code Did Well
✅ Boilerplate code generation (models, services, widgets) ✅ Firebase integration (auth, Firestore, functions) ✅ TypeScript backend services ✅ Testing infrastructure ✅ Documentation maintenance ✅ Git workflow automation
Where Human Judgment Was Critical
🧠 Product decisions (which features, UX flows) 🧠 Architecture choices (Firebase vs custom backend) 🧠 Security considerations (Firestore rules, token storage) 🧠 Business logic edge cases (multi-currency calculations) 🧠 Design aesthetics (color palette, spacing, animations)
Development observations and measurement limits
- Track complete accepted tasks and the review needed.
- Separate elapsed time, active engineering time and model usage.
- Compare against a matched baseline before claiming a speed multiplier.
- Deployment and test counts do not establish production readiness.
AI assisted implementation, while architectural decisions, security review and acceptance testing remained engineering work. No controlled productivity multiplier is established here.
Technical Challenges & Solutions
Challenge 1: distinguish alerts from background updates
An iOS alert and a background refresh are different notification behaviors. Debug the app state, permission, APNs configuration and payload type rather than assuming one flag fixes every delivery failure.
The content-available flag requests background processing; it is not generally required to display an alert. Background delivery can be throttled or skipped and does not guarantee that code runs when the app is closed.
Illustrative Firebase Admin SDK configuration for a silent background update:
// Firebase Admin SDK: silent background update, not an alert.
apns: {
headers: {
"apns-push-type": "background",
"apns-priority": "5"
},
payload: { aps: { contentAvailable: true } }
}
// Configure the iOS background capability and handler separately.
// Delivery and background execution are not guaranteed.Verify alert presentation and background refresh separately on a physical device. Background modes, permissions, force-quit state and system scheduling can affect behavior.
Challenge 2: Multi-Currency Balance Calculations
Binary floating-point arithmetic can introduce monetary rounding errors. Rounding a USD balance to a whole double discards cents; use integer minor units or a suitable decimal representation with explicit currency rules.
Illustrative integer-cent arithmetic for USD. Parse decimal input exactly at the boundary, check supported ranges and apply an explicit rounding rule to currency conversion:
// Illustrative USD arithmetic: integer cents, not rounded dollars.
// Values below are synthetic. 1,025 cents + 35 cents = 1,060 cents.
int balanceUSDCents = 1025;
const int amountUSDCents = 35;
balanceUSDCents += amountUSDCents;
assert(balanceUSDCents == 1060);
// On confirmed review, persist the transaction, posting marker and balance change
// atomically on the trusted backend. Conversions need explicit
// currency scales, rate timestamps and a documented rounding rule.Test fractional units, negative amounts, reversals, duplicate events, currency conversion and concurrent updates. A test count alone is not evidence that the money model is correct.
Challenge 3: Gmail OAuth Token Management
Problem: Tokens expire, Pub/Sub watch expires after 7 days, users lose email processing capability.
Solution:
- Scheduled hourly token refresh
- Automatic Pub/Sub watch renewal
- Graceful fallback if refresh fails
- User notification when re-auth needed
Challenge 4: Email Parsing Variability
Problem: Banks change email formats, different languages (Spanish/English), inconsistent merchant names.
Solution:
- Use deterministic parsing where formats are stable and model assistance where it is justified.
- Reuse reviewed corrections as selected context.
- Treat model confidence as uncalibrated until evaluated.
- Require review for uncertain, invalid or policy-sensitive records.
Model-based parsing and deterministic rules solve different problems. Evaluate them on representative formats and route uncertain or invalid results to review.
System Architecture
Here's how all the pieces fit together:
graph TB
subgraph "User Device - iOS"
A[FinHub Flutter App]
B[FCM SDK]
C[Firestore SDK]
end
subgraph "Firebase Cloud"
D[Firebase Auth]
E[Cloud Firestore]
F[Cloud Functions]
G[Cloud Messaging]
end
subgraph "External APIs"
H[Gmail API]
I[Gmail Pub/Sub]
J[Claude AI API]
end
A --> D
A --> C
C --> E
B --> G
H --> I
I --> F
F --> E
F --> J
F --> G
G --> B
style A fill:#bb9af7
style E fill:#9ece6a
style F fill:#e0af68
style J fill:#bb9af7
Email Processing Flow
sequenceDiagram
participant Bank
participant Gmail
participant PubSub as Gmail Pub/Sub
participant CF as Trusted Backend
participant Claude as Claude AI
participant FS as Firestore
participant FCM
participant Phone as iPhone
Bank->>Gmail: Send transaction email
Gmail->>PubSub: Publish change signal
PubSub->>CF: Enqueue mailbox synchronization
CF->>Gmail: Synchronize history and fetch authorized message
Gmail-->>CF: Email content
CF->>CF: Parse and validate candidate
CF->>Claude: Request category suggestion
Claude-->>CF: Untrusted suggestion
CF->>FS: Atomically persist deduplicated pending candidate and review outbox
Note over CF,FS: Ledger and balance unchanged
CF->>FCM: Deliver outbox notification with retry
FCM-->>Phone: Review alert when delivery succeeds
Phone->>CF: Confirm reviewed candidate and revision
CF->>FS: Atomically post ledger and balance once, mark candidate confirmed
FS-->>CF: Existing posting or new committed result
The result: an implementation to evaluate
What Got Built:
The implementation explored Flutter screens, Gmail change events, model-assisted categorization, currency conversion and mobile notifications. The architecture and code below describe responsibilities to verify; they do not certify the app as secure or production-ready.
Operational questions before production use:
- Measure observed application availability separately from provider SLA terms.
- Measure latency, retries and recovery on representative workloads.
- Evaluate cost using published rates and measured resource categories.
- Test capacity and correctness before making scalability claims.
User Experience:
- User makes purchase at grocery store
- Bank sends a notification according to its own timing.
- Gmail receives email
- Pub/Sub triggers Cloud Function
- Claude AI categorizes as "🥩 Groceries"
- A deduplicated pending candidate and review outbox entry are saved to Firestore; the balance is unchanged.
- Push notification appears on iPhone lock screen
- User taps notification → app opens to review
- User reviews amount, currency, wallet and category, then confirms or rejects the candidate.
- On confirmation, the trusted backend atomically posts the ledger record and balance change once and marks the candidate confirmed. Replays return the existing result.
The two-week development narrative is an account of the build process, not a controlled productivity experiment or evidence of complete operational readiness.
Lessons Learned & The Future of AI Development
Key Insights
1. AI Amplifies, Not Replaces
AI assisted code generation and integration. Human judgment remained necessary for architecture, UX, security and acceptance; no measured percentage attribution is established here.
The magic happens in the collaboration. AI generates, human validates. AI implements, human architects. AI tests, human designs.
2. Serverless = Speed
Firebase manages infrastructure components and provides scaling features. Application owners must configure access control, credential storage, backups, recovery, quotas and monitoring. Serverless does not make these responsibilities disappear.
Trade-off: Vendor lock-in and cold starts. But for a solo developer? Worth it.
3. AI-Native Product Design
Model categorization can use prior examples and handle varied descriptions; rules can be preferable for stable formats. Evaluate both. A model-reported confidence score is not a calibrated probability and must not automatically bypass review without validation.
This isn't "we added AI." This is "AI is fundamental to how the product works."
4. Testing is Non-Negotiable
Financial software needs tests for arithmetic, authorization, retries, idempotency and recovery. AI-generated tests are useful only when their assertions independently reflect the intended behavior.
5. Modern Stack = Modern Speed
- Flutter: Beautiful iOS UI with minimal code
- Firebase: Backend without backend
- TypeScript: Type safety in Cloud Functions
- Claude AI: Intelligent features without ML expertise
The stack matters. Choose tools designed for rapid iteration.
What This Means for Development
AI-assisted development can shorten some implementation steps. Production readiness depends on the system’s tested behavior and operating requirements, not the calendar alone.
A small team can prototype a substantial integration, while specialist review and operational support may still be necessary.
The AI-Native Era
We're entering a paradigm shift where:
- Apps ARE intelligent (Claude AI categorization, not just UI)
- Development IS AI-assisted (Claude Code, Cursor, Bug Bot)
- Deployment needs validation (timing alone does not establish readiness)
- Quality needs evidence (test behavior, recovery and authorization)
FinHub represents this future:
- Built WITH AI (Claude Code for development)
- Powered BY AI (Claude API for categorization)
- Deployed ON serverless (Firebase scales automatically)
- Developed over two weeks, with production readiness evaluated separately.
For Developers:
Use the tools to test a concrete product idea, then measure whether the implementation meets its requirements.
Start with a bounded prototype and explicit acceptance criteria. Add the deployment controls required by the data and actions the product handles.
For Founders:
Prototype speed and production readiness are different milestones. Plan for security review, correctness, observability and failure recovery alongside feature work.
A useful advantage is delivering a reliable result for the intended user, not merely being first to deploy.
The Cyberpunk Future is Now:
We're living in a world where:
- Category suggestions can reuse selected reviewed examples.
- Emails become structured data automatically
- Notifications are asynchronous and can arrive late or fail.
- One developer builds what used to require a team
- AI writes itself into the product
Technical Deep Dive: Architecture & Code
Data Model
erDiagram
USER ||--o{ PENDING_CANDIDATE : reviews
PENDING_CANDIDATE ||--o| TRANSACTION : posts_once
USER ||--o{ TRANSACTION : confirms
USER ||--o{ WALLET : owns
USER ||--o{ CATEGORY : defines
USER ||--o{ KEYWORD_MAPPING : reviews
PENDING_CANDIDATE {
string id PK
string userId FK
string sourceKey
int revision
int amountMinorUnits
string currency
string state
}
TRANSACTION {
string id PK
string userId FK
string candidateId FK
string postingKey
int amountMinorUnits
string currency
string category
string walletId FK
timestamp timestamp
string note
string type
string aiConfidence
}
WALLET {
string id PK
string userId FK
string name
int balanceMinorUnits
string currency
bool isDefault
}
CATEGORY {
string id PK
string userId FK
string emoji
string description
bool isProtected
}
AI Learning Loop
graph TD
A[Email Arrives] --> B[Validate extracted candidate]
B --> C[Load selected authorized context]
C --> D[Load reviewed correction examples]
D --> E[Request category suggestion]
E --> F[Validate suggestion and exact amount]
F --> G[Persist deduplicated pending candidate and review outbox]
G --> H[User reviews amount currency wallet and category]
H --> I{Explicit confirmation}
I -->|Confirm| J[Atomic one-time ledger and balance update]
I -->|Reject| K[Mark rejected without monetary write]
J --> L[Store reviewed example for later prompts]
L --> C
style E fill:#bb9af7
style J fill:#9ece6a
style L fill:#e0af68
Key Code: Cloud Function Email Processing
ILLUSTRATIVE PSEUDOCODE — not a deployable handler.
On an authenticated Gmail change signal:
Resolve the authorized mailbox and enqueue synchronization.
Serialize processing per mailbox using a durable worker/lease
with fencing so an expired worker cannot advance its checkpoint.
Read OAuth credentials from server-only storage.
Read the last successfully processed Gmail history cursor.
If no cursor exists:
Perform the documented full-sync procedure for the required scope.
Persist pending candidates idempotently and establish a valid baseline.
Do not use the incoming notification cursor as an initial sync.
Otherwise:
Keep startHistoryId fixed at the saved cursor for this sync.
Call users.history.list; consume every nextPageToken.
For every added message:
Fetch and validate the authorized message and exact monetary fields.
Use mailbox identity + message ID as a stable ingestion key.
Atomically persist a pending candidate and review-notification outbox,
or recognize an existing candidate (including confirmed/rejected ones).
Do not write a transaction or change any balance during ingestion.
After all pages and pending writes succeed, checkpoint the final
response's historyId under the same serialized/fenced ownership.
User review may occur later; it must not hold the mailbox cursor open.
Never advance the cursor past failed pending-candidate persistence.
If Gmail returns 404 for an expired history cursor:
Full-sync with the same pending-candidate idempotency guarantees.
On transient failure:
Retry without advancing the saved cursor.
Deliver review outbox notifications separately with retry/deduplication.
Renew the Gmail watch and reconcile periodically for missed signals.
On an authenticated confirmation of a candidate revision:
Recheck ownership, wallet authorization, revision and exact monetary fields.
In one database transaction:
If the candidate is already confirmed, return its existing posting.
Reject a stale revision or a candidate that is no longer pending.
Create one ledger record under the stable candidate posting key.
Apply the signed balance change and mark the candidate confirmed.
Retry conflicts without applying a second balance change.
On rejection:
Atomically mark the pending candidate rejected; do not post money.
Later category-only corrections must not reapply the monetary amount.
Required implementation/tests: auth, lease fencing, full sync, pagination,
exact parsing, pending persistence, concurrent confirmation, replay,
rejection, stale revisions, atomic posting, outbox and recovery.
Key Code: Flutter Real-time Transaction Stream
class TransactionService {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final FirebaseAuth _auth = FirebaseAuth.instance;
/// Stream of transactions for current user
Stream<List<Transaction>> streamTransactions({
String? categoryFilter,
String? walletFilter,
}) {
final user = _auth.currentUser;
if (user == null) return Stream.value([]);
Query query = _firestore
.collection('transactions')
.where('userId', isEqualTo: user.uid)
.orderBy('timestamp', descending: true)
.limit(100);
// Apply filters
if (categoryFilter != null) {
query = query.where('category', isEqualTo: categoryFilter);
}
if (walletFilter != null) {
query = query.where('walletId', isEqualTo: walletFilter);
}
return query.snapshots().map((snapshot) {
return snapshot.docs.map((doc) {
final data = doc.data() as Map<String, dynamic>;
data['id'] = doc.id;
return Transaction.fromJson(data);
}).toList();
});
}
}
Tech Stack Summary
| Layer | Technology | Purpose | Why Chosen |
|---|---|---|---|
| Frontend | Flutter 3.32.7 | iOS app | Single codebase, hot reload, native performance |
| Dart 3.8.1 | Language | Type-safe, async/await, great tooling | |
| Backend | Firebase | Serverless | Managed infrastructure; configure auth, quotas and recovery |
| Cloud Firestore | NoSQL DB | Real-time streams, offline support | |
| Cloud Functions | Compute | TypeScript, event-driven, auto-scale | |
| AI | Claude Console | Categorization | Category suggestions from selected context |
| Gmail API | Email fetch | OAuth 2.0, reliable, well-documented | |
| Gmail Pub/Sub | Webhooks | Asynchronous change signals and reconciliation | |
| Dev Tools | Claude Code | AI coding | Boilerplate generation, Git automation |
| Cursor | AI editor | Context completions, refactoring | |
| Language | TypeScript | Functions | Type safety, better tooling |
Conclusion
The useful result is an inspectable integration: events become pending candidates, models propose categories, and users confirm or reject the monetary details before posting. Every automated write needs a defined correctness and recovery contract.
Flutter, Firebase and Claude provide useful building blocks. The architecture still needs exact money handling, authorized access, reliable synchronization and measured behavior before it can support production claims.
The AI-native era rewards builders. Not planners. Not waiters. Builders.