offline_first_sync 1.1.5 copy "offline_first_sync: ^1.1.5" to clipboard
offline_first_sync: ^1.1.5 copied to clipboard

Intelligent offline-first data management with automatic sync, conflict resolution, and visual status indicators

offline_first_sync #

An intelligent offline-first data management package for Flutter that automatically syncs when online, handles conflicts, and provides visual indicators of sync status.

Features #

  • 🔄 Automatic Sync: Intelligent syncing when network becomes available
  • 📱 Offline-First: All operations work offline with local storage
  • Conflict Resolution: Multiple strategies for handling sync conflicts
  • 🎨 Visual Indicators: Beautiful UI components for sync status
  • 🗄️ Local Storage: SQLite-based local persistence
  • 🌐 Network Aware: Automatic network connectivity detection
  • 🔧 Customizable: Flexible sync intervals and strategies

Installation #

Add this to your package's pubspec.yaml file:

dependencies:
  offline_first_sync: ^1.1.5

Quick Start #

1. Initialize SyncManager #

import 'package:offline_first_sync/offline_first_sync.dart';

final syncClient = HttpSyncClient(
  baseUrl: 'https://your-api.com/api',
  headers: {'Authorization': 'Bearer your-token'},
);

final syncManager = SyncManager(
  client: syncClient,
  syncInterval: Duration(minutes: 5),
);

2. Add Sync Items #

// Create a new item
await syncManager.addSyncItem(
  entityType: 'todos',
  entityId: 'todo-123',
  data: {'title': 'Buy groceries', 'completed': false},
  action: SyncAction.create,
);

// Update an existing item
await syncManager.addSyncItem(
  entityType: 'todos',
  entityId: 'todo-123',
  data: {'title': 'Buy groceries', 'completed': true},
  action: SyncAction.update,
);

// Delete an item
await syncManager.addSyncItem(
  entityType: 'todos',
  entityId: 'todo-123',
  data: {},
  action: SyncAction.delete,
);

3. Monitor Sync Status #

StreamBuilder<SyncStatus>(
  stream: syncManager.statusStream,
  builder: (context, snapshot) {
    final status = snapshot.data;
    return SyncStatusIndicator(
      status: status,
      onTap: () => showSyncDetails(),
    );
  },
)

4. Handle Conflicts #

syncManager.conflictsStream.listen((conflicts) {
  for (final conflict in conflicts) {
    // Show conflict resolution UI
    showConflictResolutionDialog(conflict);
  }
});

// Resolve conflict
await syncManager.resolveConflict(
  conflictItem.id,
  ConflictResolutionStrategy.clientWins,
);

Core Classes #

SyncManager #

The main class that orchestrates offline-first data synchronization.

Constructor:

SyncManager({
  required SyncClient client,
  LocalStorage? storage,
  Duration syncInterval = const Duration(minutes: 5),
})

Key Methods:

  • addSyncItem() - Adds an operation to the sync queue
  • sync() - Manually triggers synchronization
  • resolveConflict() - Resolves sync conflicts
  • retryFailed() - Retries failed sync operations
  • clearSyncQueue() - Clears all pending sync items

Streams:

  • statusStream - Real-time sync status updates
  • conflictsStream - Stream of conflict items requiring resolution

SyncItem #

Represents a single synchronization operation.

Properties:

  • id - Unique identifier for the sync operation
  • entityType - Type of entity being synced (e.g., 'todos', 'users')
  • entityId - Unique identifier of the entity
  • data - The data to be synchronized
  • action - Type of operation (create, update, delete)
  • state - Current sync state (pending, syncing, synced, failed, conflict)
  • createdAt - When the sync item was created
  • lastSyncAttempt - Last time sync was attempted
  • retryCount - Number of retry attempts
  • conflictVersion - Server version causing conflict
  • serverData - Server data in case of conflict

SyncStatus #

Provides comprehensive information about the current synchronization state.

Properties:

  • isOnline - Whether the device has internet connectivity
  • isSyncing - Whether a sync operation is currently in progress
  • pendingCount - Number of items waiting to be synced
  • failedCount - Number of items that failed to sync
  • conflictCount - Number of items with sync conflicts
  • lastSyncTime - Timestamp of the last successful sync
  • errorMessage - Last error message, if any

Computed Properties:

  • hasIssues - Returns true if there are failed or conflicted items
  • hasPendingItems - Returns true if there are pending items
  • isFullySynced - Returns true if everything is synchronized

ConflictResolution #

Represents the resolution of a sync conflict.

Constructor:

ConflictResolution({
  required String syncItemId,
  required ConflictResolutionStrategy strategy,
  Map<String, dynamic>? resolvedData,
  required DateTime resolvedAt,
})

Properties:

  • syncItemId - ID of the sync item that had the conflict
  • strategy - The resolution strategy that was applied
  • resolvedData - The final data after conflict resolution (optional, used with manual strategy)
  • resolvedAt - Timestamp when the conflict was resolved

Usage Example:

final resolution = ConflictResolution(
  syncItemId: 'sync-item-123',
  strategy: ConflictResolutionStrategy.manual,
  resolvedData: {'title': 'Merged title', 'completed': true},
  resolvedAt: DateTime.now(),
);

UI Components #

SyncStatusIndicator #

A compact status indicator showing current sync state:

SyncStatusIndicator(
  status: syncStatus,
  showText: true,
  onTap: () => showSyncDetails(),
)

Properties:

  • status - Current SyncStatus
  • onTap - Callback when indicator is tapped
  • showText - Whether to show status text alongside icon

SyncFab #

A floating action button for manual sync:

SyncFab(
  status: syncStatus,
  onPressed: () => syncManager.sync(),
  tooltip: 'Sync now',
)

Properties:

  • status - Current SyncStatus
  • onPressed - Callback when FAB is pressed
  • tooltip - Optional tooltip text

Conflict Resolution Strategies #

The package provides several built-in strategies for handling sync conflicts:

ConflictResolutionStrategy.clientWins #

Always uses the local/client data, overwriting server changes.

await syncManager.resolveConflict(
  conflictId,
  ConflictResolutionStrategy.clientWins,
);

ConflictResolutionStrategy.serverWins #

Always uses the server data, discarding local changes.

await syncManager.resolveConflict(
  conflictId,
  ConflictResolutionStrategy.serverWins,
);

ConflictResolutionStrategy.lastWriteWins #

Uses the data with the most recent timestamp.

await syncManager.resolveConflict(
  conflictId,
  ConflictResolutionStrategy.lastWriteWins,
);

ConflictResolutionStrategy.merge #

Automatically attempts to merge both versions of the data.

await syncManager.resolveConflict(
  conflictId,
  ConflictResolutionStrategy.merge,
);

ConflictResolutionStrategy.manual #

Allows custom data to be provided for conflict resolution.

await syncManager.resolveConflict(
  conflictId,
  ConflictResolutionStrategy.manual,
  customData: {'title': 'User edited title', 'completed': true},
);

Custom Sync Client #

Implement your own sync client by extending the SyncClient abstract class:

class CustomSyncClient implements SyncClient {
  @override
  Future<SyncResponse> syncItem(SyncItem item) async {
    // Your custom sync logic here
    try {
      // Perform the sync operation
      final result = await yourApiCall(item);
      
      return SyncResponse(
        success: true,
        data: result,
      );
    } catch (e) {
      return SyncResponse(
        success: false,
        error: e.toString(),
      );
    }
  }

  @override
  Future<Map<String, dynamic>?> fetchLatestData(
    String entityType,
    String entityId
  ) async {
    // Fetch the latest data from your backend
    try {
      final response = await yourFetchCall(entityType, entityId);
      return response.data;
    } catch (e) {
      return null;
    }
  }
}

Advanced Usage #

Custom Storage #

final customStorage = LocalStorage();
final syncManager = SyncManager(
  client: syncClient,
  storage: customStorage,
);

Monitoring Sync Events #

// Listen to sync status changes
syncManager.statusStream.listen((status) {
  print('Sync status: ${status.isOnline ? 'Online' : 'Offline'}');
  print('Pending items: ${status.pendingCount}');
  print('Failed items: ${status.failedCount}');
});

// Listen to conflicts
syncManager.conflictsStream.listen((conflicts) {
  print('Conflicts to resolve: ${conflicts.length}');
  for (final conflict in conflicts) {
    print('Conflict in ${conflict.entityType}: ${conflict.entityId}');
  }
});

Retry Failed Items #

await syncManager.retryFailed();

Clear Sync Queue #

await syncManager.clearSyncQueue();

Manual Sync #

await syncManager.sync();

Error Handling #

The package provides comprehensive error handling:

// Check for sync errors
if (syncStatus.errorMessage != null) {
  print('Sync error: ${syncStatus.errorMessage}');
}

// Handle failed items
if (syncStatus.failedCount > 0) {
  // Show retry option to user
  await syncManager.retryFailed();
}

// Handle conflicts
if (syncStatus.conflictCount > 0) {
  // Show conflict resolution UI
  final conflicts = await syncManager.conflictsStream.first;
  // Present resolution options to user
}

Best Practices #

  1. Initialize Early: Set up SyncManager in your app's initialization phase
  2. Handle Conflicts Gracefully: Always provide UI for conflict resolution
  3. Monitor Status: Use status indicators to keep users informed
  4. Batch Operations: Group related operations when possible
  5. Handle Errors: Implement proper error handling and retry mechanisms
  6. Test Offline: Thoroughly test your app's offline capabilities

API Reference #

Enums #

SyncAction

  • create - Create a new entity
  • update - Update an existing entity
  • delete - Delete an entity

SyncState

  • pending - Waiting to be synced
  • syncing - Currently being synced
  • synced - Successfully synchronized
  • failed - Sync failed
  • conflict - Conflict detected during sync

ConflictResolutionStrategy

  • manual - User decides resolution
  • clientWins - Always use client data
  • serverWins - Always use server data
  • lastWriteWins - Use data with latest timestamp
  • merge - Attempt to merge both versions

Example #

See the /example folder for a complete todo app implementation using offline_first_sync.

Contributing #

Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.

License #

This project is licensed under the MIT License - see the LICENSE file for details.

1
likes
140
points
6
downloads

Publisher

verified publishersanjaysharma.info

Weekly Downloads

Intelligent offline-first data management with automatic sync, conflict resolution, and visual status indicators

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

connectivity_plus, flutter, http, path, rxdart, sqflite, uuid

More

Packages that depend on offline_first_sync