Skip to content

Add hasSynced and waitForFirstSync #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 2, 2024
Merged
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
5 changes: 5 additions & 0 deletions demos/supabase-todolist/lib/models/todo_list.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:powersync/powersync.dart';
import 'package:powersync/sqlite3.dart' as sqlite;

import './todo_item.dart';
Expand Down Expand Up @@ -59,6 +60,10 @@ class TodoList {
});
}

static Stream<SyncStatus> watchSyncStatus() {
return db.statusStream;
}

/// Create a new list
static Future<TodoList> create(String name) async {
final results = await db.execute('''
Expand Down
25 changes: 19 additions & 6 deletions demos/supabase-todolist/lib/widgets/lists_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ class ListsWidget extends StatefulWidget {

class _ListsWidgetState extends State<ListsWidget> {
List<TodoList> _data = [];
bool hasSynced = false;
StreamSubscription? _subscription;
StreamSubscription? _syncStatusSubscription;

_ListsWidgetState();

Expand All @@ -68,21 +70,32 @@ class _ListsWidgetState extends State<ListsWidget> {
_data = data;
});
});
_syncStatusSubscription = TodoList.watchSyncStatus().listen((status) {
if (!context.mounted) {
return;
}
setState(() {
hasSynced = status.hasSynced ?? false;
});
});
}

@override
void dispose() {
super.dispose();
_subscription?.cancel();
_syncStatusSubscription?.cancel();
}

@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.symmetric(vertical: 8.0),
children: _data.map((list) {
return ListItemWidget(list: list);
}).toList(),
);
return !hasSynced
? const Text("Busy with sync...")
: ListView(
padding: const EdgeInsets.symmetric(vertical: 8.0),
children: _data.map((list) {
return ListItemWidget(list: list);
}).toList(),
);
}
}
5 changes: 5 additions & 0 deletions packages/powersync/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.5.1

- Adds a hasSynced flag to check if initial data has been synced.
- Adds a waitForFirstSync method to check if the first full sync has completed.

## 1.5.0

- Upgrade minimum Dart SDK constraint to `3.4.0`.
Expand Down
5 changes: 5 additions & 0 deletions packages/powersync/lib/sqlite3_common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Re-exports [sqlite3_common](https://pub.dev/packages/sqlite3) to expose sqlite3_common without
/// adding it as a direct dependency.
library;

export 'package:sqlite_async/sqlite3_common.dart';
37 changes: 35 additions & 2 deletions packages/powersync/lib/src/powersync_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class PowerSyncDatabase with SqliteQueries implements SqliteConnection {
await database.initialize();
await database.execute('SELECT powersync_init()');
await updateSchema(schema);
await _updateHasSynced();
}

/// Replace the schema with a new version.
Expand All @@ -175,6 +176,37 @@ class PowerSyncDatabase with SqliteQueries implements SqliteConnection {
return _initialized;
}

Future<void> _updateHasSynced() async {
const syncedSQL =
'SELECT 1 FROM ps_buckets WHERE last_applied_op > 0 LIMIT 1';

// Query the database to see if any data has been synced.
final result = await database.execute(syncedSQL);
final hasSynced = result.rows.isNotEmpty;

if (hasSynced != currentStatus.hasSynced) {
final status = SyncStatus(hasSynced: hasSynced);
_setStatus(status);
}
}

///
/// returns a [Future] which will resolve once the first full sync has completed.
///
Future<void> waitForFirstSync() async {
if (currentStatus.hasSynced ?? false) {
return;
}
final completer = Completer<void>();
statusStream.listen((result) {
if (result.hasSynced ?? false) {
completer.complete();
}
});

return completer.future;
}

@override
bool get closed {
return database.closed;
Expand Down Expand Up @@ -297,8 +329,9 @@ class PowerSyncDatabase with SqliteQueries implements SqliteConnection {

void _setStatus(SyncStatus status) {
if (status != currentStatus) {
currentStatus = status;
_statusStreamController.add(status);
currentStatus = status.copyWith(
hasSynced: status.hasSynced ?? status.lastSyncedAt != null);
_statusStreamController.add(currentStatus);
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/powersync/lib/src/streaming_sync.dart
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class StreamingSyncImplementation {
/// To clear errors, use [_noError] instead of null.
void _updateStatus(
{DateTime? lastSyncedAt,
bool? hasSynced,
bool? connected,
bool? connecting,
bool? downloading,
Expand All @@ -164,6 +165,7 @@ class StreamingSyncImplementation {
connected: c,
connecting: !c && (connecting ?? lastStatus.connecting),
lastSyncedAt: lastSyncedAt ?? lastStatus.lastSyncedAt,
hasSynced: hasSynced ?? lastStatus.hasSynced,
downloading: downloading ?? lastStatus.downloading,
uploading: uploading ?? lastStatus.uploading,
uploadError: uploadError == _noError
Expand Down
32 changes: 30 additions & 2 deletions packages/powersync/lib/src/sync_status.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class SyncStatus {
/// Currently this is reset to null after a restart.
final DateTime? lastSyncedAt;

/// Indicates whether there has been at least one full sync, if any.
/// Is null when unknown, for example when state is still being loaded from the database.
final bool? hasSynced;

/// Error during uploading.
///
/// Cleared on the next successful upload.
Expand All @@ -38,6 +42,7 @@ class SyncStatus {
{this.connected = false,
this.connecting = false,
this.lastSyncedAt,
this.hasSynced,
this.downloading = false,
this.uploading = false,
this.downloadError,
Expand All @@ -52,7 +57,30 @@ class SyncStatus {
other.connecting == connecting &&
other.downloadError == downloadError &&
other.uploadError == uploadError &&
other.lastSyncedAt == lastSyncedAt);
other.lastSyncedAt == lastSyncedAt &&
other.hasSynced == hasSynced);
}

SyncStatus copyWith({
bool? connected,
bool? downloading,
bool? uploading,
bool? connecting,
Object? uploadError,
Object? downloadError,
DateTime? lastSyncedAt,
bool? hasSynced,
}) {
return SyncStatus(
connected: connected ?? this.connected,
downloading: downloading ?? this.downloading,
uploading: uploading ?? this.uploading,
connecting: connecting ?? this.connecting,
uploadError: uploadError ?? this.uploadError,
downloadError: downloadError ?? this.downloadError,
lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt,
hasSynced: hasSynced ?? this.hasSynced,
);
}

/// Get the current [downloadError] or [uploadError].
Expand All @@ -68,7 +96,7 @@ class SyncStatus {

@override
String toString() {
return "SyncStatus<connected: $connected connecting: $connecting downloading: $downloading uploading: $uploading lastSyncedAt: $lastSyncedAt error: $anyError>";
return "SyncStatus<connected: $connected connecting: $connecting downloading: $downloading uploading: $uploading lastSyncedAt: $lastSyncedAt, hasSynced: $hasSynced, error: $anyError>";
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/powersync/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: powersync
version: 1.5.0
version: 1.5.1
homepage: https://powersync.com
repository: https://github.com/powersync-ja/powersync.dart
description: PowerSync Flutter SDK - keep PostgreSQL databases in sync with on-device SQLite databases.
Expand Down
Loading