Diagnostic and Debugging Tools for Ditto in Flutter
⚠️ Platform Compatibility Notice
These tools currently do not support the Flutter Web platform. They are designed for mobile (iOS, Android) and desktop (macOS, Linux) platforms where Ditto's peer-to-peer functionality and file system access are available.
📋 SDK Requirements
These tools require Ditto SDK version 4.12.1 or higher. Ensure your project uses a compatible Ditto version before integrating these diagnostic tools. You can find the latest Ditto SDK information in the Ditto Docs.
The PeerListView
provides a real-time interface for monitoring connected peers in your Ditto mesh network. This tool helps developers debug peer-to-peer connectivity and understand the network topology by displaying both local and remote peers along with their connection details.
The PeerListView
can be used as a standalone widget in your Flutter application:
import 'package:ditto_flutter_tools/ditto_flutter_tools.dart';
// In your widget build method
Scaffold(
appBar: AppBar(title: Text('Connected Peers')),
body: PeerListView(ditto: myDittoInstance),
)
The peer list view provides:
- Local Peer Information - Displays information about the current device including its peer key and cloud connectivity status
- Remote Peers List - Shows all currently connected remote peers in the mesh network
- Connection Details - Expandable tiles showing connection types and peer relationships
- Real-time Updates - Automatically updates as peers join and leave the network
- Cloud Status Indicators - Visual icons indicating which peers are connected to Ditto Cloud
For each peer (local and remote), the view shows:
- Device Name - The human-readable name of the device
- Peer Key - The unique identifier for the peer in the mesh network
- Cloud Connectivity - Icon indicating if the peer is connected to Ditto Cloud (cloud icon) or offline (cloud_off icon)
- Active Connections - Expandable list showing connection details between peers, including connection types
The PeerListView
uses Ditto's presence observer to provide real-time updates:
- Peers are automatically added when they join the network
- Peers are removed when they disconnect
- Connection status changes are reflected immediately
- No manual refresh required
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.
The PeerSyncStatusView
can be used as a standalone widget in your Flutter application:
import 'package:ditto_flutter_tools/ditto_flutter_tools.dart';
// In your widget build method
Scaffold(
appBar: AppBar(title: Text('Peer Sync Status')),
body: PeerSyncStatusView(ditto: myDittoInstance),
)
The peer sync status view provides:
- Connection Status Grouping - Separates peers into "Connected" and "Not Connected" sections for quick status overview
- Peer Type Identification - Distinguishes between Cloud Server and Peer Device connections
- Sync Session Information - Shows the synchronization session status for each peer
- Database Commit Tracking - Displays which local database commit ID each peer has synced to
- Last Update Timestamps - Shows when the last sync update was received from each peer
- Real-time Updates - Automatically updates as sync status changes
For each peer, the view shows:
- Peer Type - Either "Cloud Server" for Ditto Big Peer or "Peer Device" for mesh network peers
- Peer ID - The unique identifier for the peer
- Connection Status - Visual indicator showing if the peer is currently connected
- Sync Commit ID - The local database commit ID that this peer has synced up to (when available)
- Last Update Time - Formatted timestamp showing when the last update was received (displays as "Today", "Yesterday", or full date/time)
The PeerSyncStatusView
automatically detects your Ditto instance's DQL strict mode setting and uses the appropriate query:
This provides access to Ditto's internal synchronization metadata, giving insights into:
- Which peers have active sync sessions (
Connected
vsNot Connected
) - How up-to-date each peer is with local data changes (commit IDs)
- When synchronization last occurred with each peer (timestamps)
This view is particularly useful for:
- Debugging sync issues - Identify which peers are not receiving updates
- Monitoring sync latency - Check when peers last synchronized
- Verifying cloud connectivity - Confirm that devices are syncing with Ditto Cloud
- Understanding sync topology - See which peers are actively participating in data synchronization
- Performance monitoring - Track sync commit progress across the network
- Automatic Mode Detection: No configuration needed - works with both DQL strict modes
- Real-time Updates: Uses Ditto store observers for live sync status monitoring
- Proper Lifecycle Management: Handles widget disposal cleanly to prevent memory leaks
- Error Handling: Gracefully handles cases where sync data is unavailable
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.
The DiskUsageView
can be used as a standalone widget in your Flutter application:
import 'package:ditto_flutter_tools/ditto_flutter_tools.dart';
// In your widget build method
Scaffold(
appBar: AppBar(title: Text('Disk Usage')),
body: DiskUsageView(ditto: myDittoInstance),
)
The disk usage view provides:
- Storage Metrics - Displays the size of each file and directory within the Ditto persistence directory
- Export Database - Exports the entire Ditto database directory using the Share Dialog
- Export Logs - Exports Ditto debug logs to a file for troubleshooting
Both export features now use the native platform Share API for a seamless user experience across all supported platforms.
- ZIP Archive Creation: Creates a compressed ZIP file containing the entire Ditto database directory
- Includes All Files: Now includes lock files (
__ditto_lock*
,lock.mdb
) and system files that were previously excluded - addressing Android lock file issues - Background Processing: ZIP creation runs in a background isolate to prevent UI blocking during large database exports
- Native Sharing: Uses the platform's native share dialog to let users choose where to save or send the database export
- Automatic Cleanup: Temporary files are automatically cleaned up after sharing (success or cancellation)
- Temporary File Creation: Creates a timestamped log file (
ditto_log_[timestamp].txt
) to avoid conflicts on repeated exports - Native Sharing: Uses the platform's native share dialog for seamless export experience
- Automatic Cleanup: Temporary log files are cleaned up immediately after sharing
- Cross-Platform Consistency: Same sharing experience on iOS, Android, macOS, and Linux
- Native Integration: Users can share to any app (email, cloud storage, messaging, etc.)
- No Permission Management: No need to handle file system permissions manually
- Robust Error Handling: All errors are displayed to users via snackbar notifications
Note
Share API Advantage: Since the export functionality now uses the native Share API, no special file system permissions are required. The Share API handles all permission management automatically.
- No additional permissions needed for export functionality
- The
share_plus
package handles all platform-specific sharing requirements automatically - Users can share to any compatible app (email, cloud storage, messaging, etc.) through the native platform dialogs
The SystemSettingsView
provides a comprehensive interface for viewing and inspecting all Ditto system configuration settings. This diagnostic tool helps developers understand the current runtime configuration of their Ditto instance by displaying all system parameters retrieved using the SHOW ALL
DQL statement.
The SystemSettingsView
can be used as a standalone widget in your Flutter application:
import 'package:ditto_flutter_tools/ditto_flutter_tools.dart';
// In your widget build method
Scaffold(
appBar: AppBar(title: Text('System Settings')),
body: SystemSettingsView(ditto: myDittoInstance),
)
The system settings view provides:
- Dynamic Settings Display - Automatically displays all available system settings without hardcoded keys, adapting to different Ditto SDK versions
- Search Functionality - Real-time search/filter to quickly find specific settings by key or value
- Smart Value Formatting - Intelligent display of different data types (boolean, number, string, array, object)
- Detailed View - Expandable detail dialog for complex values (arrays, objects, long strings)
- Copy to Clipboard - Easy copying of setting key-value pairs for debugging or documentation
- Live Refresh - Manual refresh button to reload settings and see configuration changes
The view displays all system configuration parameters including but not limited to:
- Replication Settings - Parameters controlling data synchronization behavior
- Network Settings - Configuration for mesh networking, routing, and transports
- Storage Settings - Database and blob store configuration
- Performance Settings - Timeout values, batch sizes, and optimization parameters
- Feature Flags - Enabled/disabled features and experimental options
Different data types are displayed with appropriate formatting:
- Booleans - Color-coded badges (green for true, grey for false)
- Numbers - Monospace font for easy reading
- Strings - Truncated with full view on tap for long values
- Arrays/Objects - Compact badge showing type and item count, expandable for full JSON view
- Empty Values - Clear indication of empty arrays, objects, or strings
The search feature allows filtering by:
- Setting key names (e.g., searching for "replication" shows all replication-related settings)
- Setting values (e.g., searching for "true" shows all boolean settings that are enabled)
- Case-insensitive matching for convenience
- ✅ iOS: Full support
- ✅ Android: Full support
- ✅ macOS: Full support
- ✅ Linux: Full support
- ✅ Web: Full support (when Ditto Web SDK supports DQL execution)
For more information about system settings and the SHOW ALL
DQL statement, see the Ditto documentation on retrieving system values.
These tools are intended to provide insights into the status of your subscriptions.
SyncStatusHelper
provides programmatic access to the collected data, and SyncStatusView
provides a pre-made UI that can be embedded in an app for real-time debugging.
To use it, pass in your Ditto
instance, as well as a list of subscriptions you wish to monitor:
final syncStatusHelper = SyncStatusHelper(
ditto: ditto,
subscriptions: [
mySubscription1,
mySubscription2,
],
);
Alternatively, if you want to use all the subscriptions that are currently active, use the fromCurrentSubscriptions
constructor:
final syncStatusHelper = SyncStatusHelper.fromCurrentSubscriptions(
ditto: ditto,
);
Note that this will only monitor the subscriptions that are currently active. If you register a new subscription to the underlying Ditto
instance, the SyncStatusHelper
will not update.
The helper provides an overallStatus
getter, which provides a high-level overview of all subscriptions. This returns a SyncStatus
:
- if your device is not connected to any other peers,
overallStatus
will bedisconnected
- if your device is connected to at least one peer, and none of the subscriptions have been updated recently,
overallStatus
will beconnectedIdle
- if your device is connected to at least one peer, and at least one of the subscriptions has been updated recently,
overallStatus
will beconnectedSyncing
You can configure the maximum time that can have passed for an update to be considered "recent" by providing the optional idleTimeoutInterval
parameter (defaults to one second).
You can also inspect individual subscriptions:
final helper = SyncStatusHelper(/* ... */);
final subscription = helper.subscriptions[0];
// the `SyncStatus` for a particular subscription
print(helper.statusFor(subscription));
// a `DateTime?`, null if this subscription has never been updated
print(helper.lastUpdatedAt(subscription))
You can also see details about the connectivity of the device. A device is considered "connected" if and only if it is connected to at least one other peer.
final helper = SyncStatusHelper(/* ... */);
// Is the device currently connected
print(helper.isConnected);
// A `DateTime?` representing the most recent time this device was connected
// Will be `null` if this device has never been connected
print(helper.lastConnectedAt);
// `DateTime?`s representing the last time the connectivity status changed
print(helper.becameConnectedAt);
print(helper.becameDisconnectedAt);
For example, if a device had become connected to another device five minutes ago, and the connection had been uninterrupted for the entire duration:
lastConnectedAt
would be very close toDateTime.now()
(though not exactly due to slight delay in reporting connectivity changes)becameConnectedAt
would be five minutes agobecameDisconnectedAt
would benull
If, on the other hand, the device lost connectivity for one minute during that five minute period, you might see something like:
lastConnectedAt
would be very close toDateTime.now()
(though not exactly due to slight delay in reporting connectivity changes)becameConnectedAt
would be two minutes agobecameDisconnectedAt
would be three minutes ago
SyncStatusHelper
only tracks data from the point at which it was created, and cannot provide data about any point in time before its creation.
So for example, if your device was connected five minutes ago, then lost connectivity one minute ago, then you created a SyncStatusHelper
, it would report that this device had never been connected.
The sync state means:
disconnected
- you are not connected to other peersconnectedIdle
- you are connected to at least one peer and have not received recent updatesconnectedSyncing
- you are connected to at least one peer and have received at least one recent update
When connected to a big peer, being in a connectedIdle
state can be interpreted as meaning "I am up to date with what the big peer has".
However, if connected via peer-to-peer connection to another small peer, that interpretation isn't always correct.
For example:
- you could be connected to another peer, but you are islanded from the rest of the mesh
- the peer you are connected to could have a different set of sync subscriptions, and so would have incomplete data
You may also want to consider the "freshness" of data when you are disconnected. For example, consider the following scenarios:
- your device is
disconnected
, your subscription was last updated five days ago, andlastConnectedAt
is five days ago - your device is
disconnected
, your subscription was last updated five days ago, andlastConnectedAt
is 1 minute ago
In the second scenario, you can be quite confident that your data is still the most up-to-date version; it's quite unlikely that you have had no updates in the last five days, but in the one minute you've been offline, there's new data. Compare that with the first scenario, in which the last update was exactly when you lost connectivity.
That said, the aim of this tool is to provide heuristics that you can combine with an understanding of your data model to get an accurate picture of the state of your device. If you have specific knowledge about your data model or update frequency, you can use that knowledge to get a clearer view of the data you have locally.
The QueryEditorView
provides an interactive interface for executing DQL (Ditto Query Language) statements against your Ditto database. This tool helps developers test queries, inspect data, and perform CRUD operations during development and debugging, with built-in export functionality for query results.
The QueryEditorView
can be used as a standalone widget in your Flutter application:
import 'package:ditto_flutter_tools/ditto_flutter_tools.dart';
// In your widget build method
Scaffold(
appBar: AppBar(title: Text('Query Editor')),
body: QueryEditorView(ditto: myDittoInstance),
)
Or navigate to it from your debug menu:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => QueryEditorView(ditto: ditto),
),
);
The query editor provides:
- Multi-line Query Input - A text field for entering DQL statements with syntax hints
- Async Query Execution - Non-blocking query execution with loading indicator
- Paginated Results Display - Large result sets with configurable pagination (10, 25, 50, 100 items per page)
- Export Functionality - Share query results as JSON files using the native Share API
- Error Handling - Clear error messages for invalid queries or execution failures
- Multiple Query Types - Supports SELECT, INSERT, UPDATE, and DELETE operations
The query editor includes built-in export capabilities:
- Smart Export: Share button only appears when query results contain actual data (not status messages)
- JSON Format: Results are exported as raw JSON data without formatting or metadata
- Automatic Cleanup: Temporary files are automatically cleaned up after sharing
- JSON Files: Query results are exported with
.json
extension and proper MIME type - Raw Data Only: Exports only the actual query result data, no headers or formatting
The view handles multiple result types with advanced pagination:
-
Query Results (SELECT):
- Each document displayed as a separate item
- Configurable pagination (10, 25, 50, 100 items per page)
- Page navigation controls with direct page input
- Results counter showing total items and current page range
- Export functionality available for data results
-
Mutation Results (INSERT/UPDATE/DELETE):
- List of affected document IDs
- Number of documents modified
- Transaction/commit ID when available
- No export functionality (status messages only)
For comprehensive DQL syntax and examples, refer to the official Ditto documentation:
The PermissionsHealthView
provides a real-time monitoring interface for network status, helping developers debug connectivity and peer-to-peer communication issues. This feature mimics the iOS and Android versions of the Ditto Tools.
The PermissionsHealthView
can be used as a standalone widget in your Flutter application:
import 'package:ditto_flutter_tools/ditto_flutter_tools.dart';
// In your widget build method
Scaffold(
appBar: AppBar(title: Text('Permissions Health')),
body: PermissionsHealthView(),
)
The permissions health view monitors the following:
- Bluetooth Permission - Shows whether your app has been granted Bluetooth access
- Bluetooth Status - Shows if Bluetooth is enabled/disabled on the device
- Wi-Fi Status - Shows peer-to-peer WiFi capabilities (WiFi Direct/AWDL)
The implementation uses platform-specific detection to provide accurate status information:
- Real Devices: Attempts to detect actual Bluetooth and WiFi service states
- Simulators/Emulators: Shows "Not available on simulator" with appropriate messaging
- Unsupported Platforms: Shows "Unknown - Check device settings" with settings access
- ✅ iOS: Monitors Bluetooth permissions and detects simulator environments
- ✅ Android: Monitors Bluetooth permissions and detects emulator environments
- ✅ macOS: Monitors Bluetooth permissions and detects simulator environments
- ❌ Web: Not supported - displays a message indicating web platform limitations
✅ Fully Working:
- Bluetooth permission checking (all platforms)
- Simulator/emulator detection (iOS/Android)
- Settings navigation (platform-specific)
The PermissionsHealthView
uses the permission_handler
package to check and request permissions. Make sure your app includes the necessary platform-specific configurations based on the Ditto documentation at:https://docs.ditto.live/sdk/latest/install-guides/flutter#step-1%3A-add-the-ditto-dependency
Warning
This feature uses the permissions_handler package to check and request permissions. For the UI to fully function, you must follow the instructions in the permissions_handler README. Scroll to the Setup section and follow the instructions for iOS which requires to modify the Podfile in your ios directory. You will have to modify the post_install block to add in a flag to enable the Bluetooth permission so it can detect the Bluetooth status. Failure to do so will result in the Bluetooth status not being detected.
The example app includes comprehensive integration tests for the permissions health feature. These tests verify:
- ✅ Navigation Lifecycle: Ensures the permissions health screen survives navigation away and back without crashes
- ✅ Multiple Navigation Cycles: Stress tests the navigation to detect memory leaks or stream controller issues
- ✅ Real Plugin Data: Verifies
flutter_blue_plus
andpermission_handler
are working correctly - ✅ UI Components: Checks that all cards, icons, and status text display properly
- ✅ Pull to Refresh: Tests the refresh functionality
Requirements:
- Flutter 3.19+
- Connected iOS device/simulator or Android device/emulator
iOS Testing:
# Navigate to example app directory
cd example
# List available iOS simulators
flutter devices
# Run on specific iOS simulator (specify exact device name)
flutter test integration_test -d "iPhone 16 Pro Max"
# Run on physical iOS device (get device ID from flutter devices)
flutter test integration_test -d "Your-iPhone-Device-ID"
Android Testing:
# Navigate to example app directory
cd example
# List available Android devices/emulators
flutter devices
# Run on Android emulator (specify exact device ID)
flutter test integration_test -d emulator-5554
# Run on physical Android device (specify exact device ID)
flutter test integration_test -d "your-android-device-id"
Important Notes:
- You must specify a device with
-d <device-id>
- Get exact device IDs from
flutter devices
- Tests must run on actual devices/simulators (not desktop)
- Ensure Bluetooth permissions are properly configured for your platform
What the Tests Verify:
-
Navigation Lifecycle Bug Fix: The comprehensive integration test specifically checks that navigating to Permissions Health → Back → Permissions Health → Back → Permissions Health doesn't crash with stream controller errors. This was the critical bug that was fixed.
-
Real Bluetooth Status: Verifies the following states are properly detected using
flutter_blue_plus
:Bluetooth: Enabled
(when Bluetooth is on)Bluetooth: Disabled
(when Bluetooth is off)Bluetooth: Unsupported
(on simulators/unsupported devices)Bluetooth: Unavailable
(when hardware not available)
-
Real Permission Status: Verifies the following states are properly detected using
permission_handler
:Permission: Allowed Always
(when permission granted)Permission: Denied
(when permission denied)Permission: Restricted
(when permission restricted by system)
-
Lifecycle Management: Ensures the
BluetoothStatusService
singleton properly manages subscribers and doesn't leak memory or crash on repeated navigation -
Multiple Navigation Cycles: Tests perform multiple round-trip navigation cycles to stress test the lifecycle management and ensure no memory leaks or stream controller issues
Expected Results:
- ✅ All tests pass: No crashes, proper state detection, navigation works
- ✅ Console output: Tests print status information showing detected states
- ✅ No stream errors: The original navigation crash bug should be fixed
- ✅ iOS Tests Working: Integration tests now run successfully on iOS simulators and devices
Manual Testing: If integration tests can't run in your environment, manually test:
- Launch example app
- Tap "Permissions Health"
- Verify cards show real Bluetooth/permission states (not hardcoded)
- Tap back button
- Tap "Permissions Health" again
- Verify no crash occurs (this was the original bug)
- Repeat steps 4-6 multiple times to stress test
This package uses the following third-party libraries:
- Package:
archive
(^3.6.1) - Purpose: Provides ZIP compression functionality for creating database export archives, especially useful on Android where lock files can cause issues with direct file operations.
- License: BSD-3-Clause
- Repository: https://pub.dev/packages/archive
- Note: Used specifically in disk usage export functionality to create ZIP archives containing all database files (including lock files) for sharing via the Share API.
- Package:
share_plus
(^10.1.1) - Purpose: Provides native platform sharing functionality through system share dialogs, replacing manual file picker implementations.
- License: BSD-3-Clause
- Repository: https://pub.dev/packages/share_plus
- Note: Used for all export functionality (logs and database) to provide a consistent, native sharing experience across iOS, Android, macOS, and Linux platforms.
For support, please contact Ditto Support ([email protected]).