Skip to content

Commit 96ea060

Browse files
authored
Merge pull request #260 from powersync-ja/sync-progress
Sync progress status
2 parents 2c0d365 + e43e963 commit 96ea060

28 files changed

+1137
-432
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:powersync/powersync.dart' hide Column;
3+
import 'package:powersync_django_todolist_demo/powersync.dart';
4+
5+
/// A widget that shows [child] after a complete sync on the database has
6+
/// completed and a progress bar before that.
7+
class GuardBySync extends StatelessWidget {
8+
final Widget child;
9+
10+
/// When set, wait only for a complete sync within the [BucketPriority]
11+
/// instead of a full sync.
12+
final BucketPriority? priority;
13+
14+
const GuardBySync({
15+
super.key,
16+
required this.child,
17+
this.priority,
18+
});
19+
20+
@override
21+
Widget build(BuildContext context) {
22+
return StreamBuilder<SyncStatus>(
23+
stream: db.statusStream,
24+
initialData: db.currentStatus,
25+
builder: (context, snapshot) {
26+
final status = snapshot.requireData;
27+
final (didSync, progress) = switch (priority) {
28+
null => (status.hasSynced ?? false, status.downloadProgress),
29+
var priority? => (
30+
status.statusForPriority(priority).hasSynced ?? false,
31+
status.downloadProgress?.untilPriority(priority)
32+
),
33+
};
34+
35+
if (didSync) {
36+
return child;
37+
} else {
38+
return Center(
39+
child: Column(
40+
children: [
41+
const Text('Busy with sync...'),
42+
LinearProgressIndicator(value: progress?.downloadedFraction),
43+
if (progress case final progress?)
44+
Text(
45+
'${progress.downloadedOperations} out of ${progress.totalOperations}')
46+
],
47+
),
48+
);
49+
}
50+
},
51+
);
52+
}
53+
}

demos/django-todolist/lib/widgets/lists_page.dart

+18-23
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import 'package:flutter/material.dart';
22

3-
import '../powersync.dart';
43
import './list_item.dart';
54
import './list_item_dialog.dart';
65
import '../main.dart';
76
import '../models/todo_list.dart';
7+
import 'guard_by_sync.dart';
88

99
void _showAddDialog(BuildContext context) async {
1010
return showDialog<void>(
@@ -40,32 +40,27 @@ class ListsPage extends StatelessWidget {
4040
}
4141
}
4242

43-
class ListsWidget extends StatelessWidget {
43+
final class ListsWidget extends StatelessWidget {
4444
const ListsWidget({super.key});
4545

4646
@override
4747
Widget build(BuildContext context) {
48-
return FutureBuilder(
49-
future: db.waitForFirstSync(),
50-
builder: (context, snapshot) {
51-
return switch (snapshot.connectionState) {
52-
ConnectionState.done => StreamBuilder(
53-
stream: TodoList.watchListsWithStats(),
54-
builder: (context, snapshot) {
55-
final items = snapshot.data ?? const [];
56-
57-
return ListView(
58-
padding: const EdgeInsets.symmetric(vertical: 8.0),
59-
children: items.map((list) {
60-
return ListItemWidget(list: list);
61-
}).toList(),
62-
);
63-
},
64-
),
65-
// waitForFirstSync() did not complete yet
66-
_ => const Text('Busy with sync...'),
67-
};
68-
},
48+
return GuardBySync(
49+
child: StreamBuilder(
50+
stream: TodoList.watchListsWithStats(),
51+
builder: (context, snapshot) {
52+
if (snapshot.data case final todoLists?) {
53+
return ListView(
54+
padding: const EdgeInsets.symmetric(vertical: 8.0),
55+
children: todoLists.map((list) {
56+
return ListItemWidget(list: list);
57+
}).toList(),
58+
);
59+
} else {
60+
return const CircularProgressIndicator();
61+
}
62+
},
63+
),
6964
);
7065
}
7166
}

demos/django-todolist/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
ignoresPersistentStateOnLaunch = "NO"
6060
debugDocumentVersioning = "YES"
6161
debugServiceExtension = "internal"
62+
enableGPUValidationMode = "1"
6263
allowLocationSimulation = "YES">
6364
<BuildableProductRunnable
6465
runnableDebuggingMode = "0">

demos/django-todolist/pubspec.lock

+5-5
Original file line numberDiff line numberDiff line change
@@ -310,21 +310,21 @@ packages:
310310
path: "../../packages/powersync"
311311
relative: true
312312
source: path
313-
version: "1.11.3"
313+
version: "1.12.4"
314314
powersync_core:
315315
dependency: "direct overridden"
316316
description:
317317
path: "../../packages/powersync_core"
318318
relative: true
319319
source: path
320-
version: "1.1.3"
320+
version: "1.2.4"
321321
powersync_flutter_libs:
322322
dependency: "direct overridden"
323323
description:
324324
path: "../../packages/powersync_flutter_libs"
325325
relative: true
326326
source: path
327-
version: "0.4.5"
327+
version: "0.4.7"
328328
pub_semver:
329329
dependency: transitive
330330
description:
@@ -438,10 +438,10 @@ packages:
438438
dependency: transitive
439439
description:
440440
name: sqlite3_web
441-
sha256: "870f287c2375117af1f769893c5ea0941882ee820444af5c3dcceec3b217aab1"
441+
sha256: "967e076442f7e1233bd7241ca61f3efe4c7fc168dac0f38411bdb3bdf471eb3c"
442442
url: "https://pub.dev"
443443
source: hosted
444-
version: "0.3.0"
444+
version: "0.3.1"
445445
sqlite_async:
446446
dependency: "direct main"
447447
description:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:powersync/powersync.dart' hide Column;
3+
import 'package:powersync_flutter_demo/powersync.dart';
4+
5+
/// A widget that shows [child] after a complete sync on the database has
6+
/// completed and a progress bar before that.
7+
class GuardBySync extends StatelessWidget {
8+
final Widget child;
9+
10+
/// When set, wait only for a complete sync within the [BucketPriority]
11+
/// instead of a full sync.
12+
final BucketPriority? priority;
13+
14+
const GuardBySync({
15+
super.key,
16+
required this.child,
17+
this.priority,
18+
});
19+
20+
@override
21+
Widget build(BuildContext context) {
22+
return StreamBuilder<SyncStatus>(
23+
stream: db.statusStream,
24+
initialData: db.currentStatus,
25+
builder: (context, snapshot) {
26+
final status = snapshot.requireData;
27+
final (didSync, progress) = switch (priority) {
28+
null => (status.hasSynced ?? false, status.downloadProgress),
29+
var priority? => (
30+
status.statusForPriority(priority).hasSynced ?? false,
31+
status.downloadProgress?.untilPriority(priority)
32+
),
33+
};
34+
35+
if (didSync) {
36+
return child;
37+
} else {
38+
return Center(
39+
child: Column(
40+
children: [
41+
const Text('Busy with sync...'),
42+
LinearProgressIndicator(value: progress?.downloadedFraction),
43+
if (progress case final progress?)
44+
Text(
45+
'${progress.downloadedOperations} out of ${progress.totalOperations}')
46+
],
47+
),
48+
);
49+
}
50+
},
51+
);
52+
}
53+
}

demos/supabase-todolist/lib/widgets/lists_page.dart

+18-22
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import 'package:flutter/material.dart';
22
import 'package:powersync/powersync.dart';
3-
import 'package:powersync_flutter_demo/powersync.dart';
43

54
import './list_item.dart';
65
import './list_item_dialog.dart';
76
import '../main.dart';
87
import '../models/todo_list.dart';
8+
import 'guard_by_sync.dart';
99

1010
void _showAddDialog(BuildContext context) async {
1111
return showDialog<void>(
@@ -46,27 +46,23 @@ class ListsWidget extends StatelessWidget {
4646

4747
@override
4848
Widget build(BuildContext context) {
49-
return FutureBuilder(
50-
future: db.waitForFirstSync(priority: _listsPriority),
51-
builder: (context, snapshot) {
52-
return switch (snapshot.connectionState) {
53-
ConnectionState.done => StreamBuilder(
54-
stream: TodoList.watchListsWithStats(),
55-
builder: (context, snapshot) {
56-
final items = snapshot.data ?? const [];
57-
58-
return ListView(
59-
padding: const EdgeInsets.symmetric(vertical: 8.0),
60-
children: items.map((list) {
61-
return ListItemWidget(list: list);
62-
}).toList(),
63-
);
64-
},
65-
),
66-
// waitForFirstSync() did not complete yet
67-
_ => const Text('Busy with sync...'),
68-
};
69-
},
49+
return GuardBySync(
50+
priority: _listsPriority,
51+
child: StreamBuilder(
52+
stream: TodoList.watchListsWithStats(),
53+
builder: (context, snapshot) {
54+
if (snapshot.data case final todoLists?) {
55+
return ListView(
56+
padding: const EdgeInsets.symmetric(vertical: 8.0),
57+
children: todoLists.map((list) {
58+
return ListItemWidget(list: list);
59+
}).toList(),
60+
);
61+
} else {
62+
return const CircularProgressIndicator();
63+
}
64+
},
65+
),
7066
);
7167
}
7268

packages/powersync_core/lib/powersync_core.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export 'src/exceptions.dart';
1010
export 'src/log.dart';
1111
export 'src/open_factory.dart';
1212
export 'src/schema.dart';
13-
export 'src/sync_status.dart';
13+
export 'src/sync/sync_status.dart'
14+
hide BucketProgress, InternalSyncDownloadProgress;
1415
export 'src/uuid.dart';

packages/powersync_core/lib/src/database/core_version.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ extension type const PowerSyncCoreVersion((int, int, int) _tuple) {
6060
// Note: When updating this, also update the download URL in
6161
// scripts/init_powersync_core_binary.dart and the version ref in
6262
// packages/sqlite3_wasm_build/build.sh
63-
static const minimum = PowerSyncCoreVersion((0, 3, 11));
63+
static const minimum = PowerSyncCoreVersion((0, 3, 14));
6464

6565
/// The first version of the core extensions that this version of the Dart
6666
/// SDK doesn't support.

packages/powersync_core/lib/src/database/native/native_powersync_database.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import 'package:meta/meta.dart';
55
import 'package:http/http.dart' as http;
66
import 'package:logging/logging.dart';
77
import 'package:powersync_core/src/abort_controller.dart';
8-
import 'package:powersync_core/src/bucket_storage.dart';
8+
import 'package:powersync_core/src/sync/bucket_storage.dart';
99
import 'package:powersync_core/src/connector.dart';
1010
import 'package:powersync_core/src/database/powersync_database.dart';
1111
import 'package:powersync_core/src/database/powersync_db_mixin.dart';
@@ -15,8 +15,8 @@ import 'package:powersync_core/src/log_internal.dart';
1515
import 'package:powersync_core/src/open_factory/abstract_powersync_open_factory.dart';
1616
import 'package:powersync_core/src/open_factory/native/native_open_factory.dart';
1717
import 'package:powersync_core/src/schema.dart';
18-
import 'package:powersync_core/src/streaming_sync.dart';
19-
import 'package:powersync_core/src/sync_status.dart';
18+
import 'package:powersync_core/src/sync/streaming_sync.dart';
19+
import 'package:powersync_core/src/sync/sync_status.dart';
2020
import 'package:sqlite_async/sqlite3_common.dart';
2121
import 'package:sqlite_async/sqlite_async.dart';
2222
// ignore: implementation_imports

packages/powersync_core/lib/src/database/powersync_db_mixin.dart

+22-11
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import 'package:powersync_core/src/powersync_update_notification.dart';
1313
import 'package:powersync_core/src/schema.dart';
1414
import 'package:powersync_core/src/schema_logic.dart';
1515
import 'package:powersync_core/src/schema_logic.dart' as schema_logic;
16-
import 'package:powersync_core/src/sync_status.dart';
16+
import 'package:powersync_core/src/sync/sync_status.dart';
1717

1818
mixin PowerSyncDatabaseMixin implements SqliteConnection {
1919
/// Schema used for the local database.
@@ -208,16 +208,27 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
208208
@visibleForTesting
209209
void setStatus(SyncStatus status) {
210210
if (status != currentStatus) {
211-
// Note that currently the streaming sync implementation will never set hasSynced.
212-
// lastSyncedAt implies that syncing has completed at some point (hasSynced = true).
213-
// The previous values of hasSynced should be preserved here.
214-
final newStatus = status.copyWith(
215-
hasSynced: status.lastSyncedAt != null
216-
? true
217-
: status.hasSynced ?? currentStatus.hasSynced,
218-
lastSyncedAt: status.lastSyncedAt ?? currentStatus.lastSyncedAt);
219-
// If the absence of hasSync was the only difference, the new states would be equal
220-
// and don't require an event. So, check again.
211+
final newStatus = SyncStatus(
212+
connected: status.connected,
213+
downloading: status.downloading,
214+
uploading: status.uploading,
215+
connecting: status.connecting,
216+
uploadError: status.uploadError,
217+
downloadError: status.downloadError,
218+
priorityStatusEntries: status.priorityStatusEntries,
219+
downloadProgress: status.downloadProgress,
220+
// Note that currently the streaming sync implementation will never set
221+
// hasSynced. lastSyncedAt implies that syncing has completed at some
222+
// point (hasSynced = true).
223+
// The previous values of hasSynced should be preserved here.
224+
lastSyncedAt: status.lastSyncedAt ?? currentStatus.lastSyncedAt,
225+
hasSynced: status.lastSyncedAt != null
226+
? true
227+
: status.hasSynced ?? currentStatus.hasSynced,
228+
);
229+
230+
// If the absence of hasSynced was the only difference, the new states
231+
// would be equal and don't require an event. So, check again.
221232
if (newStatus != currentStatus) {
222233
currentStatus = newStatus;
223234
statusStreamController.add(currentStatus);

packages/powersync_core/lib/src/database/web/web_powersync_database.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import 'package:meta/meta.dart';
33
import 'package:fetch_client/fetch_client.dart';
44
import 'package:logging/logging.dart';
55
import 'package:powersync_core/src/abort_controller.dart';
6-
import 'package:powersync_core/src/bucket_storage.dart';
6+
import 'package:powersync_core/src/sync/bucket_storage.dart';
77
import 'package:powersync_core/src/connector.dart';
88
import 'package:powersync_core/src/database/powersync_database.dart';
99
import 'package:powersync_core/src/database/powersync_db_mixin.dart';
1010
import 'package:powersync_core/src/log.dart';
1111
import 'package:powersync_core/src/open_factory/abstract_powersync_open_factory.dart';
1212
import 'package:powersync_core/src/open_factory/web/web_open_factory.dart';
1313
import 'package:powersync_core/src/schema.dart';
14-
import 'package:powersync_core/src/streaming_sync.dart';
14+
import 'package:powersync_core/src/sync/streaming_sync.dart';
1515
import 'package:sqlite_async/sqlite_async.dart';
1616

1717
import '../../web/sync_controller.dart';

0 commit comments

Comments
 (0)