Skip to content

Commit aff84cd

Browse files
authored
Merge pull request #42 from powersync-ja/improve-status
Improve connection status
2 parents df5c555 + 0e2fa08 commit aff84cd

File tree

7 files changed

+248
-61
lines changed

7 files changed

+248
-61
lines changed

demos/supabase-anonymous-auth/lib/widgets/status_app_bar.dart

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22

3+
import 'package:flutter/foundation.dart';
34
import 'package:flutter/material.dart';
45
import 'package:powersync/powersync.dart';
56
import '../powersync.dart';
@@ -39,22 +40,48 @@ class _StatusAppBarState extends State<StatusAppBar> {
3940

4041
@override
4142
Widget build(BuildContext context) {
42-
const connectedIcon = IconButton(
43-
icon: Icon(Icons.wifi),
44-
tooltip: 'Connected',
45-
onPressed: null,
46-
);
47-
const disconnectedIcon = IconButton(
48-
icon: Icon(Icons.wifi_off),
49-
tooltip: 'Not connected',
50-
onPressed: null,
51-
);
43+
final statusIcon = _getStatusIcon(_connectionState);
5244

5345
return AppBar(
5446
title: Text(widget.title),
5547
actions: <Widget>[
56-
_connectionState.connected ? connectedIcon : disconnectedIcon
48+
statusIcon,
49+
// Make some space for the "Debug" banner, so that the status
50+
// icon isn't hidden
51+
if (kDebugMode) _makeIcon('Debug mode', Icons.developer_mode),
5752
],
5853
);
5954
}
6055
}
56+
57+
Widget _makeIcon(String text, IconData icon) {
58+
return Tooltip(
59+
message: text,
60+
child: SizedBox(width: 40, height: null, child: Icon(icon, size: 24)));
61+
}
62+
63+
Widget _getStatusIcon(SyncStatus status) {
64+
if (status.anyError != null) {
65+
// The error message is verbose, could be replaced with something
66+
// more user-friendly
67+
if (!status.connected) {
68+
return _makeIcon(status.anyError!.toString(), Icons.cloud_off);
69+
} else {
70+
return _makeIcon(status.anyError!.toString(), Icons.sync_problem);
71+
}
72+
} else if (status.connecting) {
73+
return _makeIcon('Connecting', Icons.cloud_sync_outlined);
74+
} else if (!status.connected) {
75+
return _makeIcon('Not connected', Icons.cloud_off);
76+
} else if (status.uploading && status.downloading) {
77+
// The status changes often between downloading, uploading and both,
78+
// so we use the same icon for all three
79+
return _makeIcon('Uploading and downloading', Icons.cloud_sync_outlined);
80+
} else if (status.uploading) {
81+
return _makeIcon('Uploading', Icons.cloud_sync_outlined);
82+
} else if (status.downloading) {
83+
return _makeIcon('Downloading', Icons.cloud_sync_outlined);
84+
} else {
85+
return _makeIcon('Connected', Icons.cloud_queue);
86+
}
87+
}

demos/supabase-edge-function-auth/lib/widgets/status_app_bar.dart

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22

3+
import 'package:flutter/foundation.dart';
34
import 'package:flutter/material.dart';
45
import 'package:powersync/powersync.dart';
56
import '../powersync.dart';
@@ -39,22 +40,48 @@ class _StatusAppBarState extends State<StatusAppBar> {
3940

4041
@override
4142
Widget build(BuildContext context) {
42-
const connectedIcon = IconButton(
43-
icon: Icon(Icons.wifi),
44-
tooltip: 'Connected',
45-
onPressed: null,
46-
);
47-
const disconnectedIcon = IconButton(
48-
icon: Icon(Icons.wifi_off),
49-
tooltip: 'Not connected',
50-
onPressed: null,
51-
);
43+
final statusIcon = _getStatusIcon(_connectionState);
5244

5345
return AppBar(
5446
title: Text(widget.title),
5547
actions: <Widget>[
56-
_connectionState.connected ? connectedIcon : disconnectedIcon
48+
statusIcon,
49+
// Make some space for the "Debug" banner, so that the status
50+
// icon isn't hidden
51+
if (kDebugMode) _makeIcon('Debug mode', Icons.developer_mode),
5752
],
5853
);
5954
}
6055
}
56+
57+
Widget _makeIcon(String text, IconData icon) {
58+
return Tooltip(
59+
message: text,
60+
child: SizedBox(width: 40, height: null, child: Icon(icon, size: 24)));
61+
}
62+
63+
Widget _getStatusIcon(SyncStatus status) {
64+
if (status.anyError != null) {
65+
// The error message is verbose, could be replaced with something
66+
// more user-friendly
67+
if (!status.connected) {
68+
return _makeIcon(status.anyError!.toString(), Icons.cloud_off);
69+
} else {
70+
return _makeIcon(status.anyError!.toString(), Icons.sync_problem);
71+
}
72+
} else if (status.connecting) {
73+
return _makeIcon('Connecting', Icons.cloud_sync_outlined);
74+
} else if (!status.connected) {
75+
return _makeIcon('Not connected', Icons.cloud_off);
76+
} else if (status.uploading && status.downloading) {
77+
// The status changes often between downloading, uploading and both,
78+
// so we use the same icon for all three
79+
return _makeIcon('Uploading and downloading', Icons.cloud_sync_outlined);
80+
} else if (status.uploading) {
81+
return _makeIcon('Uploading', Icons.cloud_sync_outlined);
82+
} else if (status.downloading) {
83+
return _makeIcon('Downloading', Icons.cloud_sync_outlined);
84+
} else {
85+
return _makeIcon('Connected', Icons.cloud_queue);
86+
}
87+
}

demos/supabase-todolist/lib/models/todo_list.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ class TodoList {
3737
/// Watch all lists.
3838
static Stream<List<TodoList>> watchLists() {
3939
// This query is automatically re-run when data in "lists" or "todos" is modified.
40-
return db.watch('SELECT * FROM lists ORDER BY created_at').map((results) {
40+
return db
41+
.watch('SELECT * FROM lists ORDER BY created_at, id')
42+
.map((results) {
4143
return results.map(TodoList.fromRow).toList(growable: false);
4244
});
4345
}
@@ -71,7 +73,7 @@ class TodoList {
7173
/// Watch items within this list.
7274
Stream<List<TodoItem>> watchItems() {
7375
return db.watch(
74-
'SELECT * FROM todos WHERE list_id = ? ORDER BY created_at DESC',
76+
'SELECT * FROM todos WHERE list_id = ? ORDER BY created_at DESC, id',
7577
parameters: [id]).map((event) {
7678
return event.map(TodoItem.fromRow).toList(growable: false);
7779
});

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

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22

3+
import 'package:flutter/foundation.dart';
34
import 'package:flutter/material.dart';
45
import 'package:powersync/powersync.dart';
56
import 'package:powersync_flutter_demo/widgets/fts_search_delegate.dart';
@@ -40,16 +41,7 @@ class _StatusAppBarState extends State<StatusAppBar> {
4041

4142
@override
4243
Widget build(BuildContext context) {
43-
const connectedIcon = IconButton(
44-
icon: Icon(Icons.wifi),
45-
tooltip: 'Connected',
46-
onPressed: null,
47-
);
48-
const disconnectedIcon = IconButton(
49-
icon: Icon(Icons.wifi_off),
50-
tooltip: 'Not connected',
51-
onPressed: null,
52-
);
44+
final statusIcon = _getStatusIcon(_connectionState);
5345

5446
return AppBar(
5547
title: Text(widget.title),
@@ -60,8 +52,43 @@ class _StatusAppBarState extends State<StatusAppBar> {
6052
},
6153
icon: const Icon(Icons.search),
6254
),
63-
_connectionState.connected ? connectedIcon : disconnectedIcon
55+
statusIcon,
56+
// Make some space for the "Debug" banner, so that the status
57+
// icon isn't hidden
58+
if (kDebugMode) _makeIcon('Debug mode', Icons.developer_mode),
6459
],
6560
);
6661
}
6762
}
63+
64+
Widget _makeIcon(String text, IconData icon) {
65+
return Tooltip(
66+
message: text,
67+
child: SizedBox(width: 40, height: null, child: Icon(icon, size: 24)));
68+
}
69+
70+
Widget _getStatusIcon(SyncStatus status) {
71+
if (status.anyError != null) {
72+
// The error message is verbose, could be replaced with something
73+
// more user-friendly
74+
if (!status.connected) {
75+
return _makeIcon(status.anyError!.toString(), Icons.cloud_off);
76+
} else {
77+
return _makeIcon(status.anyError!.toString(), Icons.sync_problem);
78+
}
79+
} else if (status.connecting) {
80+
return _makeIcon('Connecting', Icons.cloud_sync_outlined);
81+
} else if (!status.connected) {
82+
return _makeIcon('Not connected', Icons.cloud_off);
83+
} else if (status.uploading && status.downloading) {
84+
// The status changes often between downloading, uploading and both,
85+
// so we use the same icon for all three
86+
return _makeIcon('Uploading and downloading', Icons.cloud_sync_outlined);
87+
} else if (status.uploading) {
88+
return _makeIcon('Uploading', Icons.cloud_sync_outlined);
89+
} else if (status.downloading) {
90+
return _makeIcon('Downloading', Icons.cloud_sync_outlined);
91+
} else {
92+
return _makeIcon('Connected', Icons.cloud_queue);
93+
}
94+
}

packages/powersync/lib/src/powersync_database.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ class PowerSyncDatabase with SqliteQueries implements SqliteConnection {
4242
final SqliteDatabase database;
4343

4444
/// Current connection status.
45-
SyncStatus currentStatus =
46-
const SyncStatus(connected: false, lastSyncedAt: null);
45+
SyncStatus currentStatus = const SyncStatus();
4746

4847
/// Use this stream to subscribe to connection status updates.
4948
late final Stream<SyncStatus> statusStream;
@@ -200,8 +199,8 @@ class PowerSyncDatabase with SqliteQueries implements SqliteConnection {
200199
final SyncStatus status = data[1];
201200
_setStatus(status);
202201
} else if (action == 'close') {
203-
_setStatus(SyncStatus(
204-
connected: false, lastSyncedAt: currentStatus.lastSyncedAt));
202+
// Clear status apart from lastSyncedAt
203+
_setStatus(SyncStatus(lastSyncedAt: currentStatus.lastSyncedAt));
205204
rPort.close();
206205
updateSubscription?.cancel();
207206
} else if (action == 'log') {
@@ -232,6 +231,8 @@ class PowerSyncDatabase with SqliteQueries implements SqliteConnection {
232231
_disconnecter?.completeAbort();
233232
_disconnecter = null;
234233
rPort.close();
234+
// Clear status apart from lastSyncedAt
235+
_setStatus(SyncStatus(lastSyncedAt: currentStatus.lastSyncedAt));
235236
}
236237

237238
var exitPort = ReceivePort();

0 commit comments

Comments
 (0)