Skip to content

Phase 4.4: Sync retry logic, transaction safety, and SyncResult#286

Merged
nbhansen merged 1 commit intoaau-giraf:dev-mainfrom
nbhansen:feature/phase4-sync-retry
Feb 8, 2026
Merged

Phase 4.4: Sync retry logic, transaction safety, and SyncResult#286
nbhansen merged 1 commit intoaau-giraf:dev-mainfrom
nbhansen:feature/phase4-sync-retry

Conversation

@nbhansen
Copy link
Contributor

@nbhansen nbhansen commented Feb 8, 2026

Summary

Implements Phase 4.4 of the improvement roadmap — sync retry logic, SQLite transaction safety, and structured sync results.

Changes

New: RetryHelper utility (lib/src/utilities/retry_helper.dart)

  • Generic run<T>() for any async operation with retry
  • HTTP-specific runHttp() that inspects status codes before retrying
  • Exponential backoff with jitter (base 500ms, doubles per attempt)
  • Non-retryable status codes: 400, 401, 403, 404, 405, 409, 422
  • Configurable max attempts (default 3)
  • Returns RetryResult<T> with value, success flag, and attempt count

New: SyncResult model (added to sync_models.dart)

  • EntitySyncStats: per-entity-type succeeded/failed/skipped counts
  • SyncError: detailed error record with entity type, ID, message, timestamp
  • SyncResult: aggregated result replacing bool return from syncFromServer()
    • download/upload stats maps, error list, timing, success/partial getters
    • SyncResult.aborted() factory for early-exit cases

SyncDownloader — retry + transactions + stats

  • HTTP list-fetches wrapped in RetryHelper.runHttp()
  • Asset downloads (images, sounds) wrapped in RetryHelper.run()
  • All DB writes batched inside db.transaction() per entity type
  • Returns DownloadResult with synced IDs + EntitySyncStats + errors
  • Assets downloaded before transaction (network I/O outside txn scope)

SyncUploader — retry + stats

  • Multipart uploads wrapped in RetryHelper.run()
  • Board uploads use RetryHelper.runHttp() via ApiProvider
  • Returns UploadResult with EntitySyncStats + errors
  • Per-entity error tracking (was previously swallowed silently)

SyncService — structured return type

  • syncFromServer() returns SyncResult instead of bool
  • Aggregates all download/upload stats and errors
  • autoSync() also returns SyncResult

SyncTimer — exponential backoff on failures

  • Tracks consecutive failure count
  • Interval grows: 30s → 60s → 120s → … → max 5min
  • Resets to default on success
  • Uses Timer() instead of Timer.periodic() for dynamic intervals

Testing

  • flutter analyze: 0 errors in modified files
  • flutter test: 13/13 pass
  • dotnet build: 0 errors

Files changed (7)

  • + lib/src/utilities/retry_helper.dart
  • ~ lib/src/services/sync_downloader.dart
  • ~ lib/src/services/sync_uploader.dart
  • ~ lib/src/services/sync_service.dart
  • ~ lib/src/services/sync_timer.dart
  • ~ lib/src/services/sync_models.dart
  • ~ improvementPlan.md

- Create RetryHelper utility with exponential backoff + jitter for HTTP calls
  (configurable max attempts, non-retryable status codes 401/403/404)
- SyncDownloader: wrap API calls in RetryHelper.runHttp(), batch DB writes
  inside SQLite transactions, return DownloadResult with EntitySyncStats
- SyncUploader: wrap multipart uploads and postAsJson in RetryHelper,
  return UploadResult with EntitySyncStats and SyncError details
- Replace bool return from syncFromServer() with SyncResult containing
  per-entity-type download/upload stats, error list, timing
- Add SyncResult, EntitySyncStats, SyncError to sync_models.dart
- SyncTimer: exponential backoff on consecutive failures
  (30s -> 60s -> 120s -> max 5min), reset to default on success
- Mark Phase 4.4 complete in improvementPlan.md
@nbhansen nbhansen merged commit 5ad9c52 into aau-giraf:dev-main Feb 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant