Sync Architecture Overview
The app uses an offline-first pattern:
- •All writes save to local sqflite first (sync_status = 'pending')
- •A sync queue processes pending items when online
- •Firestore listeners update local DB in background
- •Conflicts detected via integer
versionfield
See docs/architecture/06_SYNC_ARCHITECTURE.md for full details.
Debugging Sync Issues
1. Data Written Locally But Not Syncing
Symptoms: Expense shows ↑ (pending) icon indefinitely.
Diagnosis steps:
- •Check
sync_queuetable for stuck items:sqlSELECT * FROM sync_queue WHERE status != 'completed' ORDER BY created_at;
- •Check if connectivity is detected:
dart
// In app: ref.read(connectivityProvider)
- •Check retry count — items with
retry_count >= 5are markedfailed:sqlSELECT * FROM sync_queue WHERE retry_count >= 5;
- •Check Firestore security rules — a rule rejection shows in Cloud Function logs
Common causes:
- •Connectivity state stale (listener not re-established after app resume)
- •Firestore security rule rejecting the write (auth token expired)
- •Sync service not started (missing initialization in app bootstrap)
- •Exponential backoff timer not resetting after reconnect
2. Data Visible in Firestore But Not in Local UI
Symptoms: Other users see the expense but this device doesn't.
Diagnosis steps:
- •Check if Firestore listener is active for the collection:
dart
// Verify listener exists in FirestoreListenerManager
- •Check if the listener's
onDatacallback is writing to sqflite - •Check if the sqflite DAO emits stream updates after write
- •Check if the provider is watching the correct stream
Common causes:
- •Listener disposed too early (screen popped before sync complete)
- •Listener not re-established after auth token refresh
- •sqflite DAO not using
notifyListenerspattern - •Provider watching wrong group/collection
3. Duplicate Entries After Sync
Symptoms: Same expense appears twice.
Diagnosis steps:
- •Check if IDs are truly duplicated or just visually similar
- •Check sync queue — was the same operation enqueued twice?
sql
SELECT entity_id, COUNT(*) FROM sync_queue GROUP BY entity_id HAVING COUNT(*) > 1;
- •Check if Firestore listener re-processed an existing document
Common causes:
- •Missing idempotency check in listener callback (should be UPSERT, not INSERT)
- •Sync queue item not marked
completedafter successful Firestore write - •Race condition between sync queue processing and listener receiving the write-back
4. Conflict Detection Issues
Symptoms: Edits from another device silently overwritten.
Diagnosis steps:
- •Check
versionfield on both local and remote document - •Check conflict resolution logic:
- •Delete always wins
- •Last-write-wins for non-critical fields (description, notes)
- •User prompt for critical fields (amount, splits)
- •Check if
conflictstatus is being set correctly:sqlSELECT * FROM expenses WHERE sync_status = 'conflict';
Common causes:
- •Version field not incremented on local edit
- •Conflict detection comparing wrong fields
- •User conflict resolution UI not shown (missing provider state)
5. Stale Data After Reconnect
Symptoms: App shows old balances after going online.
Diagnosis steps:
- •Check if Firestore listeners resume after connectivity change
- •Check if balance recalculation triggered on sync
- •Check timestamp of last successful sync
Common causes:
- •Connectivity listener not firing on WiFi↔cellular transitions
- •Firestore SDK cache not invalidated
- •Balance provider not re-computing after sync
Sync Queue States
code
pending → processing → completed
→ failed (after 5 retries)
→ conflict (version mismatch detected)
Key Tables for Debugging
sql
-- Pending sync items SELECT * FROM sync_queue WHERE status = 'pending' ORDER BY created_at; -- Failed items SELECT * FROM sync_queue WHERE status = 'failed'; -- Conflicts SELECT * FROM sync_queue WHERE status = 'conflict'; -- Items with entities that have mismatched sync status SELECT e.id, e.sync_status, sq.status as queue_status FROM expenses e LEFT JOIN sync_queue sq ON sq.entity_id = e.id AND sq.entity_type = 'expense' WHERE e.sync_status != 'synced';
Reference
- •Sync architecture:
docs/architecture/06_SYNC_ARCHITECTURE.md - •Algorithms (conflict resolution):
docs/architecture/10_ALGORITHMS.md