offline_sync_outbox
A lightweight offline-first operation queue for Dart and Flutter. It keeps API mutations in strict FIFO order, persists them as JSON, resumes when connectivity returns, and applies configurable exponential backoff when requests fail.
Why use it?
Mobile connections disappear at inconvenient moments. UI code should not need to
know whether a mutation was sent immediately, queued for later, or retried after a
temporary server failure. offline_sync_outbox isolates that responsibility
behind a small processor and storage abstraction.
Features
- Strict FIFO processing for deterministic server-side ordering.
- Durable, atomic-style JSON file persistence for Dart IO platforms.
- In-memory store for tests and ephemeral workflows.
- Configurable exponential backoff and maximum attempt count.
- Optional server-directed retry delays.
- Automatic resume through a pluggable connectivity stream.
- Serialized store access, including concurrent enqueue calls.
- Duplicate operation ID protection.
- Lifecycle events and per-pass synchronization reports.
- Zero runtime package dependencies.
Installation
For Flutter:
flutter pub add offline_sync_outbox
For Dart:
dart pub add offline_sync_outbox
Quick start
final sync = OfflineSyncManager(
store: JsonFileSyncStore('sync_queue.json'),
processor: (op) async => sendToApi(op)
? const SyncResult.success()
: const SyncResult.retry(),
);
await sync.initialize();
await sync.enqueue(action: 'create_order', payload: {'orderId': 42});
sendToApi represents your HTTP, gRPC, or repository-layer call. Return
SyncResult.success() after the server accepts the mutation,
SyncResult.retry() for temporary failures, or SyncResult.discard() for
permanent validation failures.
Persistent storage
Pass an application-owned file path to JsonFileSyncStore. In Flutter, resolve
that path with the platform storage solution your app already uses. The store
writes a temporary file and retains a backup while replacing the live queue.
final store = JsonFileSyncStore('/app-data/offline-queue.json');
JsonFileSyncStore supports Dart IO platforms: Android, iOS, Windows, macOS,
Linux, and server-side Dart. For Flutter Web, implement SyncStore with IndexedDB
or another browser persistence layer.
Only JSON-encodable values should be placed in an operation payload. Create one manager per persistent store path.
Connectivity
The default connectivity source is always online. Adapt any connectivity package or application service by implementing three members:
final class AppConnectivity implements SyncConnectivity {
AppConnectivity(this.service);
final NetworkService service;
@override
Stream<bool> get changes => service.onlineChanges;
@override
Future<bool> isOnline() => service.isOnline();
@override
Future<void> dispose() async {}
}
When the stream emits true, the manager automatically starts a pass if
autoSync is enabled.
Retries
final policy = SyncRetryPolicy(
maxAttempts: 6,
initialDelay: const Duration(seconds: 2),
maxDelay: const Duration(minutes: 2),
multiplier: 2,
);
A failed head operation blocks newer operations until it succeeds, is discarded, or exhausts its retry budget. This head-of-line behavior is deliberate: it avoids reordering related mutations such as create, update, then delete.
Processors can override the calculated delay, for example after an HTTP 429:
return const SyncResult.retry(
reason: 'rate limited',
retryAfter: Duration(seconds: 30),
);
Unhandled processor exceptions are converted into retry results automatically.
Observability
sync.events.listen((event) {
print('${event.type}: ${event.operation?.id}');
});
final report = await sync.synchronize();
print('synced=${report.succeeded}, pending=${report.pending}');
Lifecycle
Call initialize() before queue access and dispose() when the manager is no
longer needed. By default, disposing the manager also disposes its connectivity
source. Set disposeConnectivity: false when that source is shared.
Architecture
The package separates policy from infrastructure:
OfflineSyncManagerserializes mutations and controls the FIFO loop.SyncStoreowns persistence; JSON and memory implementations are included.SyncProcessormaps application-specific API behavior to aSyncResult.SyncConnectivitysupplies online state without coupling to a plugin.SyncRetryPolicycalculates bounded exponential delays.
See example/offline_sync_outbox_example.dart
for a complete runnable example.
Contributing
Issues and pull requests are welcome. See CONTRIBUTING.md for the local validation commands.
License
MIT. See LICENSE.
Libraries
- offline_sync_outbox
- Offline-first FIFO synchronization for Dart and Flutter applications.