flutter_offline_sync_queue 0.1.0 copy "flutter_offline_sync_queue: ^0.1.0" to clipboard
flutter_offline_sync_queue: ^0.1.0 copied to clipboard

Offline-first background sync for Flutter. Queue any local model while offline and automatically sync it to any REST API when connectivity returns. Supports WorkManager background tasks, notifications [...]

offline_sync_kit #

Offline-first background sync for Flutter.

Queue any local Dart model while offline, then automatically sync it to any REST API when connectivity returns — with WorkManager background execution, local notifications, battery/lifecycle awareness, and a generic model abstraction that requires zero code generation.


Features #

Feature Details
📦 Generic queue Queue any model via a toJson lambda — no Hive annotations in your own code
🔄 Background sync WorkManager periodic task syncs every N hours while the app is closed
🛑 Foreground guard Sync pauses automatically when the user opens the app
🔋 Battery aware Skips sync below a configurable battery percentage
🌐 Connectivity + VPN Optional VPN callback; skips sync with no internet
🔁 Auto-retry Configurable max retries; permanently failed records are marked dead
🔔 Notifications Summary notification after each sync cycle
🎣 Hooks onSuccess, onFailure, onSyncStart, onSyncComplete per entity
🏗️ No code-gen Records stored as plain maps; no build_runner needed

Installation #

# pubspec.yaml
dependencies:
  offline_sync_kit: ^0.1.0

Android — AndroidManifest.xml #

Add inside the <manifest> element (outside <application>):

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

Inside <application>:

<service
    android:name="androidx.work.impl.background.systemjob.SystemJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true"/>

Quick start #

Step 1 — Declare the WorkManager dispatcher at the top level of main.dart #

⚠️ This must be a top-level function. WorkManager runs it in a separate Dart isolate that has no access to the main isolate's memory.
Re-supply your SyncConfig inside configFactory.

// main.dart — top-level, NOT inside a class
@pragma('vm:entry-point')
void myBackgroundDispatcher() {
  BackgroundSyncDispatcher.callbackDispatcher(
    configFactory: () => SyncConfig(
      baseUrl: 'https://api.example.com',
      // Read token from SharedPreferences / secure storage inside the isolate
      getAuthToken: () async => 'Bearer ${await MyAuth.getSavedToken()}',
      entities: buildSyncEntities(),
    ),
  );
}

Step 2 — Initialise in main() #

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Tracks foreground/background — must be called before runApp
  AppLifecycleObserver.initialize();

  await OfflineSyncKit.initialize(
    backgroundDispatcher: myBackgroundDispatcher,
    config: SyncConfig(
      baseUrl: 'https://api.example.com',
      getAuthToken: () async => 'Bearer ${await MyAuth.getToken()}',
      entities: buildSyncEntities(),
      showSyncNotifications: true,
      onSyncComplete: (ok, fail) => print('Sync done ✅$ok ❌$fail'),
    ),
  );

  runApp(const MyApp());
}

Step 3 — Define entity configs #

List<SyncEntityConfig> buildSyncEntities() => [
  // Create
  SyncEntityConfig<Order>(
    boxKey: 'create_orders',          // unique Hive box name — never rename
    endpoint: '/orders',
    method: HttpMethod.post,
    toJson: (o) => o.toJson(),
    extractServerId: (data) => data['id']?.toString(),
    onSuccess: (r) => print('Created on server: ${r.serverId}'),
    maxRetries: 3,
  ),

  // Update — URL suffix contains the server id
  SyncEntityConfig<Map<String, dynamic>>(
    boxKey: 'update_orders',
    endpoint: '/orders',
    method: HttpMethod.patch,
    buildPathSuffix: (record) => '/${record.serverId}',
    toJson: (m) => m,
  ),

  // Delete
  SyncEntityConfig<Map<String, dynamic>>(
    boxKey: 'delete_orders',
    endpoint: '/orders',
    method: HttpMethod.delete,
    buildPathSuffix: (record) => '/${record.serverId}',
    toJson: (m) => m,
    successStatusCodes: {200, 204},
  ),
];

Step 4 — Queue records anywhere #

// Queue a create
await OfflineSyncKit.queue<Order>(
  boxKey: 'create_orders',
  data: Order(item: 'Widget Pro', qty: 5),
);

// Queue an update — pass serverId so the URL suffix can use it
await OfflineSyncKit.queueRaw(
  boxKey: 'update_orders',
  payload: {'qty': 10},
  serverId: '42',
);

// Queue a delete
await OfflineSyncKit.queueRaw(
  boxKey: 'delete_orders',
  payload: {},
  serverId: '42',
);

Step 5 — (Optional) Trigger sync manually #

// On pull-to-refresh, "Sync now" button, or connectivity restored event
await OfflineSyncKit.triggerSync();

API reference #

OfflineSyncKit #

Method Returns Description
initialize(config, backgroundDispatcher?) Future<void> One-time setup. Call before runApp.
queue<T>(boxKey, data, ...) Future<String> Serialize T via toJson and enqueue.
queueRaw(boxKey, payload, ...) Future<String> Enqueue a raw Map directly.
triggerSync() Future<SyncSummary> Run sync immediately on calling isolate.
getPendingRecords(boxKey) Future<List<SyncRecord>> Pending/failed records for a box.
getAllRecords(boxKey) Future<List<SyncRecord>> All records regardless of status.
removeRecord(boxKey, localId) Future<void> Delete a specific record.
pendingCount() Future<int> Total pending count across all entities.
clearAll() Future<void> Wipe all queued data (call on logout).
stopBackgroundSync() Future<void> Cancel WorkManager tasks.

SyncConfig #

Property Type Default Description
baseUrl String required Base URL for all API calls
getAuthToken Future<String?> Function() required Returns Bearer token
entities List<SyncEntityConfig> required Entities synced in order
defaultHeaders Map<String, String> {} Added to every request
requestTimeout Duration 30s Per-request HTTP timeout
backgroundSyncInterval Duration 2h WorkManager frequency
minBatteryLevel int 20 Skip sync below this % (0 = disabled)
skipSyncWhenForeground bool true Pause sync when app is visible
showSyncNotifications bool true Show post-cycle notifications
checkVpn Future<bool> Function()? null Optional VPN check
onSyncStart void Function()? null Sync cycle start callback
onSyncComplete void Function(int, int)? null Success/fail count callback

SyncEntityConfig<T> #

Property Type Description
boxKey String Unique Hive box key. Never rename after first release.
endpoint String API path, e.g. '/orders'
method HttpMethod get, post, put, patch, delete
toJson Map<String, dynamic> Function(T) Serialises your model for Hive storage
fromJson T Function(Map)? Deserialises back (optional)
successStatusCodes Set<int> Defaults to {200, 201}
extractServerId String? Function(Map)? Reads server id from response body
buildPathSuffix String Function(SyncRecord)? Builds URL suffix, e.g. '/${record.serverId}'
onSuccess void Function(SyncResult)? Per-record success hook
onFailure void Function(SyncResult)? Per-record failure hook
maxRetries int Defaults to 3
extraHeaders Map<String, String>? Entity-specific request headers

How it works #

App foreground                      WorkManager background isolate
────────────────                    ─────────────────────────────────
OfflineSyncKit.queue()              SyncOrchestrator.run()
        │                                │
        ▼                                ├─ 1. foreground check
SyncRecord stored as                     ├─ 2. battery check
Map in Hive box                          ├─ 3. pending-data check
status: pending                          ├─ 4. connectivity check
                                         ├─ 5. auth token fetch
                                         │
                                         └─ for each SyncEntityConfig:
                                                SyncEngine.syncAll()
                                                    ├─ read pending records
                                                    ├─ HTTP request
                                                    ├─ success → delete record
                                                    └─ failure → retry / dead-letter

Showing pending count in UI #

// In a StatefulWidget
Future<void> _refresh() async {
  final count = await OfflineSyncKit.pendingCount();
  setState(() => _pendingCount = count);
}

// Show badge on a sync icon

Logout / account switch #

await OfflineSyncKit.clearAll();         // wipe all queued records
await OfflineSyncKit.stopBackgroundSync(); // cancel WorkManager tasks

License #

MIT — see LICENSE.

3
likes
0
points
163
downloads

Publisher

unverified uploader

Weekly Downloads

Offline-first background sync for Flutter. Queue any local model while offline and automatically sync it to any REST API when connectivity returns. Supports WorkManager background tasks, notifications, lifecycle awareness, battery checks, auto-retry, and a generic model abstraction with no code-generation required.

Repository (GitHub)
View/report issues

Topics

#offline #sync #background #workmanager #hive

License

unknown (license)

Dependencies

battery_plus, connectivity_plus, dio, flutter, flutter_local_notifications, hive_flutter, path_provider, shared_preferences, workmanager

More

Packages that depend on flutter_offline_sync_queue