Skip to content

Commit 432f910

Browse files
authored
Merge pull request #32 from getditto/cx-541-peer-sync-status
CX-541: Peer Sync Status
2 parents b4fe761 + 0a809b3 commit 432f910

18 files changed

+1122
-355
lines changed

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,69 @@ The `PeerListView` uses Ditto's presence observer to provide real-time updates:
5454
- No manual refresh required
5555

5656

57+
## `PeerSyncStatusView`
58+
59+
The `PeerSyncStatusView` provides a real-time interface for monitoring the synchronization status between your device and connected peers in the Ditto mesh network. This tool queries Ditto's internal `system:data_sync_info` collection to display detailed sync session information, helping developers debug data synchronization issues and monitor sync health.
60+
61+
### Usage
62+
63+
The `PeerSyncStatusView` can be used as a standalone widget in your Flutter application:
64+
65+
```dart
66+
import 'package:ditto_flutter_tools/ditto_flutter_tools.dart';
67+
68+
// In your widget build method
69+
Scaffold(
70+
appBar: AppBar(title: Text('Peer Sync Status')),
71+
body: PeerSyncStatusView(ditto: myDittoInstance),
72+
)
73+
```
74+
75+
### Features
76+
77+
The peer sync status view provides:
78+
79+
1. **Connection Status Grouping** - Separates peers into "Connected" and "Not Connected" sections for quick status overview
80+
2. **Peer Type Identification** - Distinguishes between Cloud Server and Peer Device connections
81+
3. **Sync Session Information** - Shows the synchronization session status for each peer
82+
4. **Database Commit Tracking** - Displays which local database commit ID each peer has synced to
83+
5. **Last Update Timestamps** - Shows when the last sync update was received from each peer
84+
6. **Real-time Updates** - Automatically updates as sync status changes
85+
86+
### Information Displayed
87+
88+
For each peer, the view shows:
89+
- **Peer Type** - Either "Cloud Server" for Ditto Big Peer or "Peer Device" for mesh network peers
90+
- **Peer ID** - The unique identifier for the peer
91+
- **Connection Status** - Visual indicator showing if the peer is currently connected
92+
- **Sync Commit ID** - The local database commit ID that this peer has synced up to (when available)
93+
- **Last Update Time** - Formatted timestamp showing when the last update was received (displays as "Today", "Yesterday", or full date/time)
94+
95+
### Data Source & DQL Strict Mode Support
96+
97+
The `PeerSyncStatusView` automatically detects your Ditto instance's DQL strict mode setting and uses the appropriate query:
98+
99+
This provides access to Ditto's internal synchronization metadata, giving insights into:
100+
- Which peers have active sync sessions (`Connected` vs `Not Connected`)
101+
- How up-to-date each peer is with local data changes (commit IDs)
102+
- When synchronization last occurred with each peer (timestamps)
103+
104+
### Use Cases
105+
106+
This view is particularly useful for:
107+
- **Debugging sync issues** - Identify which peers are not receiving updates
108+
- **Monitoring sync latency** - Check when peers last synchronized
109+
- **Verifying cloud connectivity** - Confirm that devices are syncing with Ditto Cloud
110+
- **Understanding sync topology** - See which peers are actively participating in data synchronization
111+
- **Performance monitoring** - Track sync commit progress across the network
112+
113+
### Implementation Notes
114+
115+
- **Automatic Mode Detection**: No configuration needed - works with both DQL strict modes
116+
- **Real-time Updates**: Uses Ditto store observers for live sync status monitoring
117+
- **Proper Lifecycle Management**: Handles widget disposal cleanly to prevent memory leaks
118+
- **Error Handling**: Gracefully handles cases where sync data is unavailable
119+
57120
## `DiskUsageView`
58121

59122
The `DiskUsageView` provides a comprehensive interface for monitoring Ditto database disk usage and exporting data for debugging or backup purposes. This tool helps developers understand storage consumption and provides convenient export functionality for both database files and logs.

example/README.md

Lines changed: 279 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ This example app showcases all the diagnostic tools available in the `ditto_flut
2525
- Track database growth over time
2626
- Identify storage optimization opportunities
2727

28+
- **Permissions Health**: Check app permissions status
29+
- Monitor Bluetooth, WiFi, and location permissions
30+
- View permission states and requirements
31+
- Get guidance on enabling required permissions
32+
33+
- **System Settings**: Access device-specific settings
34+
- Open relevant system settings pages
35+
- Quick access to Bluetooth and WiFi settings
36+
- Platform-specific configuration options
37+
38+
### 🔄 Peer Sync Status
39+
- **Peer Sync Status**: Monitor synchronization between specific peers
40+
- View sync status for individual peer connections
41+
- Track data flow between devices
42+
- Identify sync bottlenecks and issues
43+
2844
## Quick Start
2945

3046
### Prerequisites
@@ -126,27 +142,286 @@ Enable the following entitlements in `macos/Runner/DebugProfile.entitlements`:
126142
2. Tap on "Disk Usage"
127143
3. View current database size and storage metrics
128144

145+
### Monitoring Permissions Health
146+
1. Launch the app
147+
2. Tap on "Permissions Health"
148+
3. View the status of all required permissions
149+
4. Follow guidance to enable missing permissions
150+
151+
### Accessing System Settings
152+
1. Launch the app
153+
2. Tap on "System Settings"
154+
3. Access device-specific settings for Bluetooth and WiFi
155+
4. Configure network and connectivity options
156+
157+
### Viewing Peer Sync Status
158+
1. Launch the app
159+
2. Tap on "Peer Sync Status"
160+
3. Monitor synchronization between specific peers
161+
4. Track data flow and identify sync issues
162+
129163
## Project Structure
130164

131165
```
132166
example/
133167
├── lib/
134-
│ ├── main.dart # App entry point and main UI
135-
│ ├── providers/
136-
│ │ └── ditto_provider.dart # Ditto SDK initialization
168+
│ ├── main.dart # App entry point and routing configuration
169+
│ ├── constants/
170+
│ │ └── routes.dart # Centralized route definitions
137171
│ ├── services/
172+
│ │ ├── ditto_service.dart # Ditto SDK initialization and management
138173
│ │ └── subscription_service.dart # Manages sync subscriptions
174+
│ ├── screens/ # Individual screen implementations
175+
│ │ ├── peers_list_screen.dart
176+
│ │ ├── sync_status_screen.dart
177+
│ │ ├── peer_sync_status_screen.dart
178+
│ │ ├── permissions_health_screen.dart
179+
│ │ ├── disk_usage_screen.dart
180+
│ │ └── system_settings_screen.dart
139181
│ └── widgets/
140-
│ └── presence.dart # Presence visualization
182+
│ └── main_list_view.dart # Main navigation interface
141183
├── android/ # Android platform files
142184
├── ios/ # iOS platform files
143185
├── macos/ # macOS platform files
144186
├── web/ # Web platform files
145187
└── pubspec.yaml # Dependencies
146188
```
147189

190+
## Routing Architecture
191+
192+
This app uses [Beamer](https://pub.dev/packages/beamer) for navigation, providing a clean and maintainable routing system. All routes are centrally defined to avoid magic strings and ensure consistency.
193+
194+
### Route Management
195+
196+
Routes are defined in `lib/constants/routes.dart` using top-level constants:
197+
198+
```dart
199+
// Route constants - centralized location for all app routes
200+
const String homeRoute = '/';
201+
const String peersRoute = '/peers';
202+
const String syncStatusRoute = '/sync-status';
203+
const String peerSyncStatusRoute = '/peer-sync-status';
204+
const String permissionsHealthRoute = '/permissions-health';
205+
const String diskUsageRoute = '/disk-usage';
206+
const String systemSettingsRoute = '/system-settings';
207+
```
208+
209+
### Navigation Setup
210+
211+
The routing is configured in `main.dart` using Beamer's `RoutesLocationBuilder`:
212+
213+
```dart
214+
final beamerDelegate = BeamerDelegate(
215+
initialPath: homeRoute,
216+
locationBuilder: RoutesLocationBuilder(
217+
routes: {
218+
homeRoute: (context, state, data) => MainListView(
219+
dittoService: dittoService,
220+
subscriptionService: subscriptionService,
221+
),
222+
peersRoute: (context, state, data) => PeersListScreen(
223+
ditto: dittoService.ditto,
224+
),
225+
// ... other routes
226+
},
227+
),
228+
);
229+
```
230+
231+
### Navigation Usage
232+
233+
Navigate between screens using Beamer:
234+
235+
```dart
236+
// Navigate to a specific route
237+
Beamer.of(context).beamToNamed(peersRoute);
238+
239+
// Navigate back
240+
Beamer.of(context).beamBack();
241+
```
242+
148243
## Customization
149244

245+
### Adding a New Page
246+
247+
To add a new page to the app, follow these steps:
248+
249+
#### 1. Create the Route Constant
250+
251+
Add your new route to `lib/constants/routes.dart`:
252+
253+
```dart
254+
const String myNewPageRoute = '/my-new-page';
255+
```
256+
257+
#### 2. Create the Screen Widget
258+
259+
Create a new file `lib/screens/my_new_page_screen.dart`:
260+
261+
```dart
262+
import 'package:flutter/material.dart';
263+
import 'package:ditto_flutter_tools/ditto_flutter_tools.dart';
264+
265+
class MyNewPageScreen extends StatelessWidget {
266+
final Ditto ditto;
267+
268+
const MyNewPageScreen({
269+
super.key,
270+
required this.ditto,
271+
});
272+
273+
@override
274+
Widget build(BuildContext context) {
275+
return Scaffold(
276+
appBar: AppBar(
277+
title: const Text('My New Page'),
278+
),
279+
body: const Center(
280+
child: Text('This is my new page!'),
281+
),
282+
);
283+
}
284+
}
285+
```
286+
287+
#### 3. Add the Route to main.dart
288+
289+
Import your new screen and add it to the routes map in `main.dart`:
290+
291+
```dart
292+
import 'screens/my_new_page_screen.dart';
293+
import 'constants/routes.dart';
294+
295+
// In the BeamerDelegate configuration:
296+
final beamerDelegate = BeamerDelegate(
297+
initialPath: homeRoute,
298+
locationBuilder: RoutesLocationBuilder(
299+
routes: {
300+
// ... existing routes
301+
myNewPageRoute: (context, state, data) => MyNewPageScreen(
302+
ditto: dittoService.ditto,
303+
),
304+
},
305+
),
306+
);
307+
```
308+
309+
#### 4. Add Navigation Button
310+
311+
Add a navigation button to `lib/widgets/main_list_view.dart`:
312+
313+
```dart
314+
// Import the routes
315+
import '../constants/routes.dart';
316+
317+
// Add a new ListTile in the appropriate section:
318+
ListTile(
319+
leading: Container(
320+
width: 32,
321+
height: 32,
322+
decoration: BoxDecoration(
323+
color: Colors.blue,
324+
borderRadius: BorderRadius.circular(8),
325+
),
326+
child: const Icon(
327+
Icons.new_page_icon,
328+
color: Colors.white,
329+
size: 20,
330+
),
331+
),
332+
title: const Text("My New Page"),
333+
trailing: Icon(Icons.chevron_right,
334+
color: Theme.of(context).colorScheme.onSurfaceVariant),
335+
onTap: () => Beamer.of(context).beamToNamed(myNewPageRoute),
336+
),
337+
```
338+
339+
#### 5. Complete Example
340+
341+
Here's a complete example of adding a "Data Explorer" page:
342+
343+
**Step 1 - Add route constant:**
344+
```dart
345+
// In lib/constants/routes.dart
346+
const String dataExplorerRoute = '/data-explorer';
347+
```
348+
349+
**Step 2 - Create screen:**
350+
```dart
351+
// lib/screens/data_explorer_screen.dart
352+
import 'package:flutter/material.dart';
353+
import 'package:ditto_flutter_tools/ditto_flutter_tools.dart';
354+
355+
class DataExplorerScreen extends StatelessWidget {
356+
final Ditto ditto;
357+
358+
const DataExplorerScreen({
359+
super.key,
360+
required this.ditto,
361+
});
362+
363+
@override
364+
Widget build(BuildContext context) {
365+
return Scaffold(
366+
appBar: AppBar(
367+
title: const Text('Data Explorer'),
368+
),
369+
body: FutureBuilder(
370+
future: ditto.store.execute('SELECT * FROM collections'),
371+
builder: (context, snapshot) {
372+
if (snapshot.hasData) {
373+
return ListView.builder(
374+
itemCount: snapshot.data!.length,
375+
itemBuilder: (context, index) {
376+
return ListTile(
377+
title: Text('Collection ${index + 1}'),
378+
);
379+
},
380+
);
381+
}
382+
return const Center(child: CircularProgressIndicator());
383+
},
384+
),
385+
);
386+
}
387+
}
388+
```
389+
390+
**Step 3 - Add to main.dart:**
391+
```dart
392+
// Add import
393+
import 'screens/data_explorer_screen.dart';
394+
395+
// Add to routes map
396+
dataExplorerRoute: (context, state, data) => DataExplorerScreen(
397+
ditto: dittoService.ditto,
398+
),
399+
```
400+
401+
**Step 4 - Add navigation button:**
402+
```dart
403+
// In lib/widgets/main_list_view.dart, add to the SYSTEM section:
404+
ListTile(
405+
leading: Container(
406+
width: 32,
407+
height: 32,
408+
decoration: BoxDecoration(
409+
color: Colors.teal,
410+
borderRadius: BorderRadius.circular(8),
411+
),
412+
child: const Icon(
413+
Icons.explore,
414+
color: Colors.white,
415+
size: 20,
416+
),
417+
),
418+
title: const Text("Data Explorer"),
419+
trailing: Icon(Icons.chevron_right,
420+
color: Theme.of(context).colorScheme.onSurfaceVariant),
421+
onTap: () => Beamer.of(context).beamToNamed(dataExplorerRoute),
422+
),
423+
```
424+
150425
### Adding Custom Subscriptions
151426

152427
Edit `lib/services/subscription_service.dart` to add your own subscriptions:

example/lib/constants/routes.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Route constants - centralized location for all app routes
2+
const String homeRoute = '/';
3+
const String peersRoute = '/peers';
4+
const String syncStatusRoute = '/sync-status';
5+
const String peerSyncStatusRoute = '/peer-sync-status';
6+
const String permissionsHealthRoute = '/permissions-health';
7+
const String diskUsageRoute = '/disk-usage';
8+
const String systemSettingsRoute = '/system-settings';

0 commit comments

Comments
 (0)