Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ Check out [the example database](test/utils/test_database.dart) for a complete c
final syncManager = SyncManager(
localDatabase: localDatabase,
supabaseClient: supabaseClient,
onSyncStarted: (event) => showLoadingIndicator(),
onSyncCompleted: (event) => hideLoadingIndicator(),
);
```

Expand Down Expand Up @@ -190,6 +192,70 @@ It goes through the local tables for all registered syncables and sets the user
ID for all items that don't have a user ID yet.
If syncing is enabled, those items will then get synced to the backend automatically.

### Monitoring Sync State 📢

The SyncManager provides two ways to monitor sync state:

#### Simple Approach (Recommended)

For most use cases, use the built-in `ChangeNotifier` interface:

```dart
final syncManager = SyncManager(
localDatabase: localDatabase,
supabaseClient: supabaseClient,
);

// Listen to sync state changes
syncManager.addListener(() {
if (syncManager.syncInProgress) {
showLoadingIndicator();
} else {
hideLoadingIndicator();
}
});

// Or use with ValueListenableBuilder in Flutter
ValueListenableBuilder<bool>(
valueListenable: syncManager,
builder: (context, syncInProgress, child) {
return syncInProgress
? CircularProgressIndicator()
: Icon(Icons.check);
},
);
```

#### Advanced Event Notifications

For advanced use cases requiring detailed information per syncable type, enable detailed events:

```dart
final syncManager = SyncManager(
localDatabase: localDatabase,
supabaseClient: supabaseClient,
enableDetailedEvents: true, // Enable detailed events
onSyncStarted: (event) {
print('Sync started for ${event.syncableType} from ${event.source}');
// event.source can be SyncEventSource.fullSync or SyncEventSource.realtime
},
onSyncCompleted: (event) {
print('Sync completed for ${event.syncableType}: ${event.itemsReceived} items received');
// Access detailed statistics: itemsReceived, itemsUpdated, itemsDeleted
},
);
```

**When to use detailed events:**
- You need to track sync progress per table/syncable type
- You want to distinguish between full sync and real-time sync events
- You need detailed statistics about sync operations

**When to use simple approach:**
- You just want to show a loading indicator during sync
- You want minimal complexity and overhead
- You don't need per-table sync information

### Optimizations ⚡

There are a few mechanisms that can drastically reduce the ongoing data
Expand Down
3 changes: 3 additions & 0 deletions devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ generate-test-entrypoints:
# Runs all tests (with coverage)
test: generate-test-entrypoints start-supabase
supabase db test
dart test test/_test.dart --test-randomize-ordering-seed=random --coverage coverage
flutter test test/_test.dart --test-randomize-ordering-seed=random --coverage coverage
dart run coverage:format_coverage --lcov --report-on lib --check-ignore -i coverage/test/_test.dart.vm.json -o coverage/lcov.info

# Start Supabase for local development or testing
Expand Down
85 changes: 85 additions & 0 deletions lib/src/sync_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/// The source of a sync event.
enum SyncEventSource {
/// Data received from a realtime subscription to the backend.
realtime,

/// Data received from a full sync with the backend.
fullSync,
}

/// The type of sync event.
enum SyncEventType {
/// A sync operation started.
syncStarted,

/// A sync operation completed.
syncCompleted,
}

/// An event that occurs during data synchronization.
abstract class SyncEvent {
const SyncEvent({
required this.type,
required this.syncableType,
required this.source,
required this.timestamp,
});

/// The type of sync event.
final SyncEventType type;

/// The type of syncable that was affected.
final Type syncableType;

/// The source of the sync event.
final SyncEventSource source;

/// When the event occurred.
final DateTime timestamp;
}

/// An event for when a sync operation starts.
class SyncStartedEvent extends SyncEvent {
const SyncStartedEvent({
required super.syncableType,
required super.source,
required super.timestamp,
required this.reason,
}) : super(type: SyncEventType.syncStarted);

/// The reason why the sync started.
final String reason;
}

/// An event for when a sync operation completes.
class SyncCompletedEvent extends SyncEvent {
const SyncCompletedEvent({
required super.syncableType,
required super.source,
required super.timestamp,
required this.itemsReceived,
required this.itemsUpdated,
required this.itemsDeleted,
}) : super(type: SyncEventType.syncCompleted);

/// The number of items that were received (inserted) during this sync.
final int itemsReceived;

/// The number of items that were updated during this sync.
final int itemsUpdated;

/// The number of items that were deleted during this sync.
final int itemsDeleted;

/// The total number of items processed during this sync.
int get totalItemsProcessed => itemsReceived + itemsUpdated + itemsDeleted;
}

/// Callback function for sync events.
typedef SyncEventCallback = void Function(SyncEvent event);

/// Callback function specifically for sync started events.
typedef SyncStartedEventCallback = void Function(SyncStartedEvent event);

/// Callback function specifically for sync completed events.
typedef SyncCompletedEventCallback = void Function(SyncCompletedEvent event);
Loading