riverpod_offline_sync 1.0.2 copy "riverpod_offline_sync: ^1.0.2" to clipboard
riverpod_offline_sync: ^1.0.2 copied to clipboard

Production-ready offline-first sync engine for Flutter super apps with Riverpod integration

๐Ÿ“ฆ riverpod_offline_sync #

A production-ready offline-first sync engine for Flutter with Riverpod state management, queue orchestration, conflict resolution, Firebase integration, retry handling, connectivity monitoring, and built-in offline UI components.

pub version license Flutter


Table of Contents #


โœจ Features #

  • ๐Ÿ”„ Bi-directional sync (push + pull)
  • ๐Ÿ“ฆ Persistent offline queue (Hive)
  • โšก Concurrent queue processing
  • ๐Ÿ” Smart retry with exponential backoff
  • ๐Ÿ”Œ Connectivity-aware synchronization
  • ๐Ÿ“ถ WiFi-only sync mode
  • ๐Ÿง  Conflict resolution strategies
  • ๐Ÿงพ Idempotency protection
  • ๐Ÿ“Š Sync metrics & analytics
  • ๐Ÿ›  Debug inspection tools
  • ๐ŸŽจ Built-in UI widgets
  • ๐Ÿ”ฅ Firebase integrations
  • ๐Ÿงฉ Riverpod integration
  • ๐Ÿ“ฑ Offline-first architecture
  • ๐Ÿš€ Queue survives app restarts
  • ๐ŸŽฏ Upload pause / resume / cancel
  • ๐Ÿ“ˆ Real-time sync progress streams
  • ๐Ÿ”’ Production-ready sync orchestration

Dependencies #

Add the following to your pubspec.yaml:

dependencies:
  riverpod_offline_sync: ^0.1.0
  flutter_riverpod: ^2.5.0
  hive_flutter: ^1.1.0
  connectivity_plus: ^5.0.0
  synchronized: ^3.1.0

Then run:

flutter pub get

Quick Start #

1. Initialize in main() #

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_offline_sync/riverpod_offline_sync.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await OfflineSyncLayer.instance.initialize(
    config: const SyncConfig(
      autoSyncOnReconnect: true,
      syncImmediately: true,
      maxConcurrentOperations: 3,
    ),
  );

  runApp(const ProviderScope(child: MyApp()));
}

2. Register Operation Handlers #

OfflineSyncLayer.instance.registerOperationHandler(
  'orders',
  (data) async {
    await api.createOrder(data);
  },
);

OfflineSyncLayer.instance.registerOperationHandler(
  'messages',
  (data) async {
    await api.sendMessage(data);
  },
);

3. Wrap Your App #

MaterialApp(
  home: ConnectivityBanner(
    child: OfflineToast(
      child: HomePage(),
    ),
  ),
)

4. Submit Offline Operations #

await OfflineSyncLayer.instance.submitOperation(
  category: 'orders',
  priority: QueuePriority.high.value,
  data: {
    'product': 'Laptop',
    'quantity': 1,
    'price': 999.99,
  },
);

๐Ÿ— Architecture #

UI Widgets
    โ†“
Riverpod Providers
    โ†“
OfflineSyncLayer
    โ†“
QueueManager
    โ†“
Retry / Conflict / Connectivity Systems
    โ†“
Persistence Layer (Hive)
    โ†“
Backend APIs / Firebase / REST

๐Ÿ“‚ Package Structure #

lib/
 โ”œโ”€โ”€ core/
 โ”œโ”€โ”€ queue/
 โ”œโ”€โ”€ connectivity/
 โ”œโ”€โ”€ conflict/
 โ”œโ”€โ”€ firebase/
 โ”œโ”€โ”€ providers/
 โ”œโ”€โ”€ ui/
 โ”œโ”€โ”€ mixins/
 โ”œโ”€โ”€ utils/
 โ””โ”€โ”€ riverpod_offline_sync.dart

Core Concepts #

Queue Priorities #

Priority Value Use Cases
critical 0 Payments, KYC
high 1 Orders, Messages
normal 2 Profile updates
low 3 Analytics
background 4 Cache refresh

Queue Categories #

enum QueueCategory {
  uploads,
  orders,
  messages,
  payments,
  sync,
  analytics,
  media,
  documents,
  background,
}

Sync Strategies #

Strategy Description
auto Automatic synchronization
manual Manual user-triggered sync
background Silent sync
pushOnly Push local changes only
pullOnly Pull remote changes only

Conflict Resolution #

Supported strategies:

ConflictStrategy.serverWins
ConflictStrategy.clientWins
ConflictStrategy.merge
ConflictStrategy.lastWriteWins
ConflictStrategy.manualResolve

Example #

final resolver = ConflictResolver();

final resolved = await resolver.resolve(
  local: localData,
  remote: remoteData,
  strategy: ConflictStrategy.merge,
);

Features #

  • Deep merge support
  • Nested map resolution
  • List merging
  • Duplicate removal
  • Timestamp-based conflict handling

Providers #

Sync Providers #

final offlineSyncLayerProvider
final syncStateProvider
final syncProgressProvider
final syncMetricsProvider
final isSyncingProvider
final syncStatusTextProvider

Queue Providers #

final queueManagerProvider
final pendingItemsProvider
final pendingItemsCountProvider
final queueBreakdownProvider

Connectivity Providers #

final connectivityMonitorProvider
final connectivityStatusProvider
final isConnectedProvider

Methods #

OfflineSyncLayer #

Main singleton for sync orchestration.

await OfflineSyncLayer.instance.initialize();

await OfflineSyncLayer.instance.submitOperation(
  category: 'orders',
  priority: 1,
  data: {'key': 'value'},
);

await OfflineSyncLayer.instance.sync();

await OfflineSyncLayer.instance.clearQueue();

final pending =
    await OfflineSyncLayer.instance.getPendingOperations();

QueueManager #

Handles queue orchestration.

final manager = QueueManager();

await manager.initialize();

await manager.enqueue(
  category: 'orders',
  priority: 1,
  data: {},
);

await manager.processQueue(
  maxConcurrent: 3,
);

await manager.retryFailed('item_id');

StorageQueue #

Queue-based Firebase Storage uploads.

final storageQueue = StorageQueue();

await storageQueue.uploadFile(
  file: file,
  path: 'uploads/image.jpg',
  idempotencyKey: IdempotencyKey.generate(),
);

storageQueue.pauseUpload('key');
storageQueue.resumeUpload('key');
storageQueue.cancelUpload('key');

UI Components #

ConnectivityBanner #

Shows offline banner automatically.

ConnectivityBanner(
  child: HomePage(),
)

SyncStatusIndicator #

Displays sync state.

SyncStatusIndicator(
  showAsFloatingAction: true,
)

SyncProgressBar #

Displays queue progress.

SyncProgressBar(
  showDetails: true,
)

OfflineToast #

Shows offline notifications.

OfflineToast(
  child: HomePage(),
)

DebugPanel #

Powerful debug and inspection tool.

showModalBottomSheet(
  context: context,
  builder: (_) => const DebugPanel(),
);

Features #

  • Queue inspection
  • Sync metrics
  • Connectivity status
  • Retry operations
  • Queue breakdown
  • Manual sync controls

Firebase Integration #

Firestore #

syncLayer.registerOperationHandler(
  'firestore_write',
  (data) async {
    await FirebaseFirestore.instance
        .collection('todos')
        .doc(data['id'])
        .set(data);
  },
);

Firebase Storage #

final storageQueue = StorageQueue();

await storageQueue.uploadFile(
  file: file,
  path: 'uploads/image.jpg',
  idempotencyKey: IdempotencyKey.generate(),
);

Supports:

  • Upload queueing
  • Pause uploads
  • Resume uploads
  • Cancel uploads
  • Progress tracking

Firebase Auth #

final auth = AuthPersistence();

await auth.signInWithEmail(
  'email@example.com',
  'password',
);

Configuration #

const config = SyncConfig(
  autoSyncOnReconnect: true,
  syncImmediately: true,
  autoSyncInterval: Duration(minutes: 15),
  maxRetries: 5,
  initialRetryDelay: Duration(seconds: 2),
  maxConcurrentOperations: 3,
  enableMetrics: true,
  enableDebugLogging: false,
  syncOnWiFiOnly: false,
  maxQueueSize: 1000,
);

Predefined Configurations #

SyncConfig.defaultConfig()
SyncConfig.aggressive()
SyncConfig.batteryFriendly()
SyncConfig.wifiOnly()

Metrics & Analytics #

Track:

  • Total syncs
  • Successful syncs
  • Failed syncs
  • Average sync duration
  • Last sync time
  • Success rate

Example:

final metrics = OfflineSyncLayer.instance.metrics;

print(metrics.successRatePercentage);

Retry System #

Features:

  • Exponential backoff
  • Delayed retry scheduling
  • Retry tracking
  • Configurable retry count

Example:

final delay =
    BackoffCalculator.calculateNextRetry(3);

Connectivity Monitoring #

Automatically:

  • Detects online/offline state
  • Syncs on reconnect
  • Supports WiFi-only mode
  • Broadcasts connectivity changes

Example:

final connected =
    OfflineSyncLayer.instance
        .connectivityMonitor
        .isConnected;

Utilities #

Idempotency Keys #

final key = IdempotencyKey.generate();

Logger #

OfflineLogger.isEnabled = true;

OfflineLogger.info('Sync started');
OfflineLogger.error('Sync failed');
OfflineLogger.debug('Queue processed');

Backoff Calculator #

final delay =
    BackoffCalculator.calculateNextRetry(3);

Examples #

Offline Orders #

await submitOffline(
  category: QueueCategory.orders.label,
  priority: QueuePriority.high.value,
  data: {
    'product': 'Laptop',
    'quantity': 1,
  },
);

Chat Messages #

await submitOffline(
  category: QueueCategory.messages.label,
  priority: QueuePriority.high.value,
  data: {
    'message': 'Hello!',
  },
);

Analytics Tracking #

await OfflineSyncLayer.instance.submitOperation(
  category: 'analytics',
  priority: QueuePriority.low.value,
  data: {
    'event_name': 'button_click',
    'timestamp': DateTime.now().toIso8601String(),
  },
);

Debugging #

Show Debug Panel #

showModalBottomSheet(
  context: context,
  builder: (_) => const DebugPanel(),
);

Manual Inspection #

final operations =
    await OfflineSyncLayer.instance
        .getPendingOperations();

final metrics =
    OfflineSyncLayer.instance.metrics;

final isConnected =
    OfflineSyncLayer.instance
        .connectivityMonitor
        .isConnected;

๐Ÿšจ Troubleshooting #

Common issues and their solutions when using riverpod_offline_sync.


1. Hive Initialization Errors #

โŒ Error: HiveError: A Hive box with name 'offline_queue' already exists #

Cause #

Multiple initializations or hot reload during development.

Solution #

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Hive before sync layer
  await Hive.initFlutter();

  // Then initialize offline sync
  await OfflineSyncLayer.instance.initialize();

  runApp(const ProviderScope(child: MyApp()));
}

โŒ Error: HiveError: Hive has not been initialized. Did you forget to call Hive.initFlutter()? #

Solution #

await Hive.initFlutter();

await OfflineSyncLayer.instance.initialize();

โŒ Error: TypeAdapter for typeId 0 is already registered #

Solution #

// The package handles this internally with HiveRegistry.ensureRegistered()
// Make sure you're not manually registering adapters

2. Connectivity Listener Issues #

โŒ Error: connectivity_plus version mismatch #

Cause #

Different versions of connectivity_plus have different APIs.

Solution #

dependencies:
  connectivity_plus: ^5.0.2

โŒ Error: StreamSubscription<ConnectivityResult> type mismatch #

Solution #

// Don't modify the connectivity_monitor.dart file
// The package handles both single and list results automatically

โŒ Error: Connectivity changes not detected #

Solution #

if (!OfflineSyncLayer.instance.connectivityMonitor.isConnected) {
  print('Not connected to network');
}

final isConnected =
    OfflineSyncLayer.instance
        .connectivityMonitor
        .isConnected;

3. Queue Stuck Processing #

โŒ Issue: Items in queue but never processed #

Option 1 โ€” Check if sync is enabled #

await OfflineSyncLayer.instance.sync();

Option 2 โ€” Check if handler is registered #

OfflineSyncLayer.instance.registerOperationHandler(
  'your_category',
  (data) async {
    // Your processing logic
  },
);

Option 3 โ€” Check retry scheduling #

await OfflineSyncLayer.instance.retryFailedOperation('item_id');

Option 4 โ€” Check queue size limits #

SyncConfig(
  maxQueueSize: 2000,
);

Option 5 โ€” Check connectivity #

if (syncConfig.syncOnWiFiOnly) {
  final isWifi =
      await OfflineSyncLayer.instance
          .connectivityMonitor
          .isWifiConnected;

  if (!isWifi) {
    print('WiFi-only mode enabled');
  }
}

4. Duplicate Operations #

โŒ Issue: Same operation submitted multiple times #

Solution 1 โ€” Use idempotency keys #

await OfflineSyncLayer.instance.submitOperation(
  category: 'orders',
  priority: 1,
  data: orderData,
  idempotencyKey:
      'unique_order_${orderData['id']}',
);

Solution 2 โ€” Generate UUID keys #

import 'package:uuid/uuid.dart';

final idempotencyKey = const Uuid().v4();

await OfflineSyncLayer.instance.submitOperation(
  category: 'orders',
  priority: 1,
  data: orderData,
  idempotencyKey: idempotencyKey,
);

Solution 3 โ€” Use built-in generator #

await OfflineSyncLayer.instance.submitOperation(
  category: 'orders',
  priority: 1,
  data: orderData,
  idempotencyKey: IdempotencyKey.generate(),
);

5. Provider Initialization Race Conditions #

โŒ Error: Null check operator used on a null value #

Solution #

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await OfflineSyncLayer.instance.initialize();

  runApp(const ProviderScope(child: MyApp()));
}

โŒ Error: OfflineSyncLayer not initialized #

Solution #

if (OfflineSyncLayer.instance.isInitialized) {
  await OfflineSyncLayer.instance.submitOperation(...);
} else {
  print('Sync layer not ready yet');
}

6. Firebase Integration Issues #

โŒ Error: [core/no-app] No Firebase App '[DEFAULT]' has been created #

Solution #

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp();

  await OfflineSyncLayer.instance.initialize();

  runApp(const ProviderScope(child: MyApp()));
}

โŒ Error: Firestore persistence enabled too late #

Solution #

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp();

  await FirebaseFirestore.instance.settings =
      const Settings(
        persistenceEnabled: true,
      );

  await OfflineSyncLayer.instance.initialize();

  runApp(const ProviderScope(child: MyApp()));
}

7. Memory Leaks #

โŒ Issue: StreamControllers not disposed #

Solution #

await OfflineSyncLayer.instance.dispose();

8. Performance Issues #

โŒ Issue: Large queue affecting performance #

Solution 1 โ€” Limit concurrent operations #

SyncConfig(
  maxConcurrentOperations: 3,
  maxQueueSize: 500,
);

Solution 2 โ€” Use background priority #

await OfflineSyncLayer.instance.submitOperation(
  category: 'analytics',
  priority: QueuePriority.background.value,
  data: analyticsData,
);

Solution 3 โ€” Enable WiFi-only mode #

SyncConfig(
  syncOnWiFiOnly: true,
);

9. Debug Panel Not Showing #

โŒ Issue: Debug panel doesn't open or shows blank #

Solution #

showModalBottomSheet(
  context: context,
  isScrollControlled: true,
  backgroundColor: Colors.transparent,
  builder: (BuildContext context) {
    return const DebugPanel();
  },
);

10. Common Setup Mistakes #

โŒ Mistake: Forgetting to register handlers #

Solution #

void main() async {
  await OfflineSyncLayer.instance.initialize();

  OfflineSyncLayer.instance.registerOperationHandler(
    'orders',
    (data) async {
      // Process order
    },
  );

  runApp(MyApp());
}

โŒ Mistake: Using providers before initialization #

Solution #

final pendingCountAsync =
    ref.watch(pendingItemsCountProvider);

final pendingCount =
    pendingCountAsync.valueOrNull ?? 0;

โŒ Mistake: Not handling async initialization in Riverpod #

Solution #

final pendingItems =
    ref.watch(pendingItemsProvider)
        .valueOrNull ?? [];

๐Ÿ” Debug Checklist #

Use this checklist when troubleshooting:

  • โŒ OfflineSyncLayer.instance.isInitialized is true
  • โŒ Handlers registered for all categories
  • โŒ Connectivity monitor working
  • โŒ Queue has items
  • โŒ No console errors
  • โŒ Hive initialized
  • โŒ Firebase initialized
  • โŒ Permissions granted
  • โŒ Not blocked by WiFi-only mode
  • โŒ Unique idempotency keys used

๐Ÿ“ž Getting Help #

Enable debug logging #

OfflineLogger.isEnabled = true;

Open debug panel #

showModalBottomSheet(
  context: context,
  builder: (_) => const DebugPanel(),
);

Inspect queue manually #

final pending =
    await OfflineSyncLayer.instance
        .getPendingOperations();

print('Pending operations: $pending');

Check sync metrics #

final metrics =
    OfflineSyncLayer.instance.metrics;

print('Total syncs: ${metrics.totalSyncs}');
print('Failed syncs: ${metrics.failedSyncs}');

Open a GitHub issue with: #

  • Error logs
  • Steps to reproduce
  • Flutter doctor output
  • Package versions

This troubleshooting guide covers the most common issues and their solutions! ๐Ÿš€


FAQ #

Q: Does it work offline? #

A: Yes. Queue operations persist locally and sync automatically later.


Q: What happens if the app restarts? #

A: Queue data persists using Hive and resumes automatically.


Q: Does it support Firebase? #

A: Yes. Firestore, Storage, and Auth integrations are included.


Q: Can I use custom backends? #

A: Yes. Works with REST APIs, GraphQL, Firebase, or any backend.


Q: How are duplicates prevented? #

A: Idempotency keys prevent duplicate operations.


Q: Does it support large uploads? #

A: Yes. Includes pause, resume, cancel, and progress tracking.


Q: Is it Riverpod-only? #

A: Core sync system works independently, but Riverpod integration is included.


License #

MIT License โ€” see LICENSE file for details.


โค๏ธ Built For Offline-First Flutter Apps #

Reliable synchronization for:

  • Super apps
  • Delivery apps
  • Chat apps
  • POS systems
  • CRM apps
  • Warehouse systems
  • Social apps
  • Field-service apps
  • Media upload apps

๐Ÿš€