Troubleshooting
Debug and resolve common issues with InstantDB Flutter applications using systematic troubleshooting techniques and diagnostic tools.
Common Issues
Connection and Sync Problems
1. “Failed to connect to InstantDB server”
Symptoms: App shows offline status, sync doesn’t work
Causes & Solutions:
// Check your app ID and configurationfinal db = await InstantDB.init( appId: 'your-correct-app-id', // Verify this is correct config: const InstantConfig( syncEnabled: true, // Use custom endpoint if needed apiUrl: 'https://api.instantdb.com', // Default websocketUrl: 'wss://api.instantdb.com/ws', // Default ),);
// Enable verbose logging to see connection detailsfinal db = await InstantDB.init( appId: 'your-app-id', config: const InstantConfig( syncEnabled: true, verboseLogging: true, // Enable detailed logs ),);
Debugging steps:
- Check your internet connection
- Verify app ID is correct
- Check firewall/proxy settings
- Enable verbose logging to see detailed error messages
2. “Data not syncing between devices”
Symptoms: Changes on one device don’t appear on others
Common causes:
// ❌ Problem: Different app IDsDevice1: InstantDB.init(appId: 'app-123')Device2: InstantDB.init(appId: 'app-456') // Wrong!
// ✅ Solution: Same app ID everywhereDevice1: InstantDB.init(appId: 'your-app-id')Device2: InstantDB.init(appId: 'your-app-id')
// ❌ Problem: Sync disabledInstantDB.init( appId: 'your-app-id', config: const InstantConfig( syncEnabled: false, // This disables sync! ),)
// ✅ Solution: Enable syncInstantDB.init( appId: 'your-app-id', config: const InstantConfig( syncEnabled: true, // Enable sync ),)
3. “Sync is slow or inconsistent”
Diagnostic widget:
class SyncDiagnostics extends StatefulWidget { @override State<SyncDiagnostics> createState() => _SyncDiagnosticsState();}
class _SyncDiagnosticsState extends State<SyncDiagnostics> { final List<String> _syncEvents = []; StreamSubscription? _connectionSubscription;
@override void initState() { super.initState(); _monitorSync(); }
void _monitorSync() { final db = InstantProvider.of(context);
// Monitor connection status changes _connectionSubscription = db.syncEngine?.connectionStatus.stream.listen((isConnected) { final event = '${DateTime.now()}: Connection ${isConnected ? 'restored' : 'lost'}'; setState(() { _syncEvents.add(event); if (_syncEvents.length > 50) { _syncEvents.removeRange(0, _syncEvents.length - 50); } }); }); }
@override Widget build(BuildContext context) { final db = InstantProvider.of(context);
return Scaffold( appBar: AppBar(title: const Text('Sync Diagnostics')), body: Column( children: [ // Current status Watch((context) { final isConnected = db.syncEngine?.connectionStatus.value ?? false; return Container( width: double.infinity, padding: const EdgeInsets.all(16), color: isConnected ? Colors.green : Colors.red, child: Text( 'Status: ${isConnected ? 'Connected' : 'Disconnected'}', style: const TextStyle(color: Colors.white, fontSize: 18), ), ); }),
// Test sync button Padding( padding: const EdgeInsets.all(16), child: ElevatedButton( onPressed: _testSync, child: const Text('Test Sync'), ), ),
// Event log Expanded( child: ListView.builder( itemCount: _syncEvents.length, itemBuilder: (context, index) { return ListTile( dense: true, title: Text( _syncEvents[index], style: const TextStyle(fontSize: 12, fontFamily: 'monospace'), ), ); }, ), ), ], ), ); }
Future<void> _testSync() async { final db = InstantProvider.of(context); final testId = db.id();
setState(() { _syncEvents.add('${DateTime.now()}: Testing sync with item $testId'); });
try { await db.transact([ ...db.create('sync_test', { 'id': testId, 'timestamp': DateTime.now().millisecondsSinceEpoch, 'test': true, }), ]);
setState(() { _syncEvents.add('${DateTime.now()}: Sync test transaction completed'); }); } catch (e) { setState(() { _syncEvents.add('${DateTime.now()}: Sync test failed: $e'); }); } }
@override void dispose() { _connectionSubscription?.cancel(); super.dispose(); }}
Authentication Issues
1. “Invalid email format” error
Problem: Email validation is too strict or has issues
// Debug email validationvoid debugEmailValidation(String email) { final isValid = RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$').hasMatch(email); print('Email: $email'); print('Valid: $isValid'); print('Length: ${email.length}');
if (!isValid) { if (email.contains(' ')) print('Contains spaces'); if (!email.contains('@')) print('Missing @ symbol'); if (!email.contains('.')) print('Missing domain extension'); }}
2. Magic code/link not working
Debugging authentication flow:
class AuthDebugScreen extends StatefulWidget { @override State<AuthDebugScreen> createState() => _AuthDebugScreenState();}
class _AuthDebugScreenState extends State<AuthDebugScreen> { final _emailController = TextEditingController(); final _codeController = TextEditingController(); final List<String> _debugLog = [];
void _log(String message) { setState(() { _debugLog.add('${DateTime.now()}: $message'); }); print(message); }
Future<void> _testMagicCode() async { final email = _emailController.text.trim(); _log('Testing magic code for: $email');
try { final db = InstantProvider.of(context);
// Step 1: Send magic code _log('Sending magic code...'); await db.auth.sendMagicCode(email); _log('Magic code sent successfully');
} catch (e) { _log('Failed to send magic code: $e');
if (e is InstantException) { _log('Error code: ${e.code}'); _log('Error message: ${e.message}');
// Specific debugging for common errors if (e.code == 'invalid_email') { _log('Email validation failed - check email format'); } else if (e.code == 'endpoint_not_found') { _log('Auth endpoint not found - check app configuration'); } } } }
Future<void> _testVerifyCode() async { final email = _emailController.text.trim(); final code = _codeController.text.trim();
_log('Verifying code: $code for email: $email');
try { final db = InstantProvider.of(context); final user = await db.auth.verifyMagicCode( email: email, code: code, );
_log('Verification successful!'); _log('User ID: ${user.id}'); _log('User email: ${user.email}');
} catch (e) { _log('Verification failed: $e');
if (e is InstantException) { _log('Error code: ${e.code}'); if (e.code == 'invalid_code') { _log('Code is invalid or expired'); } } } }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Auth Debug')), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ TextField( controller: _emailController, decoration: const InputDecoration(labelText: 'Email'), ), const SizedBox(height: 16),
ElevatedButton( onPressed: _testMagicCode, child: const Text('Test Send Magic Code'), ), const SizedBox(height: 16),
TextField( controller: _codeController, decoration: const InputDecoration(labelText: 'Magic Code'), ), const SizedBox(height: 16),
ElevatedButton( onPressed: _testVerifyCode, child: const Text('Test Verify Code'), ), const SizedBox(height: 24),
// Debug log Expanded( child: Container( width: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[100], border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), ), child: ListView.builder( itemCount: _debugLog.length, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Text( _debugLog[index], style: const TextStyle(fontSize: 12, fontFamily: 'monospace'), ), ); }, ), ), ), ], ), ), ); }}
Query Issues
1. “Query returns no results”
Debug query structure:
class QueryDebugger { static void debugQuery(InstantDB db, Map<String, dynamic> query) { print('=== Query Debug ==='); print('Query: ${jsonEncode(query)}');
// Test the query db.queryOnce(query).then((result) { print('Result: ${result.data}'); print('Error: ${result.error}');
if (result.data != null) { result.data!.forEach((entityType, entities) { final count = (entities as List).length; print('$entityType: $count items'); }); } }).catchError((error) { print('Query failed: $error'); }); }
static void debugEntityStructure(InstantDB db, String entityType) { print('=== Entity Structure Debug ===');
// Get a sample of entities to see structure db.queryOnce({ entityType: {'limit': 5} }).then((result) { final entities = result.data?[entityType] as List? ?? [];
if (entities.isEmpty) { print('No $entityType entities found'); return; }
print('Sample $entityType entities:'); for (int i = 0; i < entities.length; i++) { final entity = entities[i] as Map<String, dynamic>; print('Entity $i: ${entity.keys.join(', ')}'); if (i == 0) { // Show full structure of first entity entity.forEach((key, value) { print(' $key: ${value.runtimeType} = $value'); }); } } }); }}
// Usagevoid testQuery() { final query = { 'todos': { 'where': {'completed': false}, 'orderBy': {'createdAt': 'desc'}, } };
QueryDebugger.debugQuery(db, query); QueryDebugger.debugEntityStructure(db, 'todos');}
2. “Widget not updating when data changes”
Debug reactive updates:
class ReactivityDebugger extends StatefulWidget { final Map<String, dynamic> query;
const ReactivityDebugger({super.key, required this.query});
@override State<ReactivityDebugger> createState() => _ReactivityDebuggerState();}
class _ReactivityDebuggerState extends State<ReactivityDebugger> { final List<String> _updateLog = []; late Signal<QueryResult> _querySignal;
@override void initState() { super.initState(); final db = InstantProvider.of(context); _querySignal = db.subscribeQuery(widget.query);
// Monitor signal changes _querySignal.toStream().listen((result) { final timestamp = DateTime.now().toString(); setState(() { _updateLog.add('$timestamp: Query updated'); if (_updateLog.length > 20) { _updateLog.removeRange(0, _updateLog.length - 20); } }); }); }
@override Widget build(BuildContext context) { return Column( children: [ // Current data Watch((context) { final result = _querySignal.value; final hasData = result.data != null; final hasError = result.error != null;
return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: hasError ? Colors.red[100] : hasData ? Colors.green[100] : Colors.grey[100], border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Status: ${hasError ? 'Error' : hasData ? 'Data' : 'Loading'}'), if (hasError) Text('Error: ${result.error}'), if (hasData) Text('Data keys: ${result.data!.keys.join(', ')}'), ], ), ); }),
const SizedBox(height: 16),
// Update log Text('Update Log (${_updateLog.length}):'), Expanded( child: ListView.builder( itemCount: _updateLog.length, itemBuilder: (context, index) { return Text( _updateLog[index], style: const TextStyle(fontSize: 12, fontFamily: 'monospace'), ); }, ), ), ], ); }}
Diagnostic Tools
Logging Configuration
Set up comprehensive logging:
import 'package:logging/logging.dart';
void setupLogging() { // Set up hierarchical logging Logger.root.level = Level.ALL; Logger.root.onRecord.listen((record) { final timestamp = record.time.toString().substring(11, 23); final level = record.level.name.padRight(7); final logger = record.loggerName.padRight(20);
print('$timestamp $level $logger ${record.message}');
if (record.error != null) { print(' Error: ${record.error}'); }
if (record.stackTrace != null) { print(' Stack: ${record.stackTrace}'); } });}
// Custom logger for InstantDB componentsclass InstantDBLogger { static final _logger = Logger('InstantDB');
static void debug(String message) => _logger.fine(message); static void info(String message) => _logger.info(message); static void warning(String message) => _logger.warning(message); static void error(String message, [Object? error, StackTrace? stackTrace]) { _logger.severe(message, error, stackTrace); }}
Database Inspector
Create a database inspection tool:
class DatabaseInspector extends StatefulWidget { @override State<DatabaseInspector> createState() => _DatabaseInspectorState();}
class _DatabaseInspectorState extends State<DatabaseInspector> { String? _selectedEntity; Map<String, dynamic>? _entityData;
@override Widget build(BuildContext context) { final db = InstantProvider.of(context);
return Scaffold( appBar: AppBar( title: const Text('Database Inspector'), actions: [ IconButton( onPressed: _exportData, icon: const Icon(Icons.download), ), ], ), body: Row( children: [ // Entity list SizedBox( width: 200, child: _buildEntityList(db), ),
const VerticalDivider(),
// Entity data Expanded( child: _selectedEntity != null ? _buildEntityData(db, _selectedEntity!) : const Center(child: Text('Select an entity')), ), ], ), ); }
Widget _buildEntityList(InstantDB db) { // List of known entity types - you might want to make this configurable final entityTypes = ['users', 'posts', 'comments', 'todos', 'sync_test'];
return ListView.builder( itemCount: entityTypes.length, itemBuilder: (context, index) { final entityType = entityTypes[index]; return ListTile( title: Text(entityType), selected: _selectedEntity == entityType, onTap: () => _selectEntity(entityType), ); }, ); }
Widget _buildEntityData(InstantDB db, String entityType) { return InstantBuilder( query: {entityType: {'limit': 100}}, builder: (context, result) { if (result.error != null) { return Center(child: Text('Error: ${result.error}')); }
final entities = (result.data?[entityType] as List? ?? []) .cast<Map<String, dynamic>>();
return Column( children: [ // Header Container( padding: const EdgeInsets.all(16), child: Row( children: [ Text( '$entityType (${entities.length} items)', style: Theme.of(context).textTheme.headlineSmall, ), const Spacer(), ElevatedButton( onPressed: () => _addTestEntity(db, entityType), child: const Text('Add Test Item'), ), ], ), ),
// Data table Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: SingleChildScrollView( child: _buildDataTable(entities), ), ), ), ], ); }, ); }
Widget _buildDataTable(List<Map<String, dynamic>> entities) { if (entities.isEmpty) { return const Center(child: Text('No data')); }
// Get all unique keys final Set<String> allKeys = {}; for (final entity in entities) { allKeys.addAll(entity.keys); }
final keys = allKeys.toList()..sort();
return DataTable( columns: keys.map((key) => DataColumn(label: Text(key))).toList(), rows: entities.map((entity) { return DataRow( cells: keys.map((key) { final value = entity[key]; return DataCell( Text( value?.toString() ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, ), ); }).toList(), ); }).toList(), ); }
void _selectEntity(String entityType) { setState(() { _selectedEntity = entityType; }); }
Future<void> _addTestEntity(InstantDB db, String entityType) async { final testData = { 'id': db.id(), 'test': true, 'createdAt': DateTime.now().millisecondsSinceEpoch, 'name': 'Test ${entityType.substring(0, entityType.length - 1)}', };
try { await db.transact([ ...db.create(entityType, testData), ]);
ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Test $entityType created')), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to create test $entityType: $e')), ); } }
void _exportData() { // Export database data for debugging print('Database export requested'); // Implementation depends on your export requirements }}
Performance Issues
Memory Leaks
Detect and fix memory leaks:
class MemoryLeakDetector { static final Map<String, int> _widgetCounts = {};
static void registerWidget(String widgetName) { _widgetCounts[widgetName] = (_widgetCounts[widgetName] ?? 0) + 1; }
static void unregisterWidget(String widgetName) { _widgetCounts[widgetName] = (_widgetCounts[widgetName] ?? 1) - 1; if (_widgetCounts[widgetName]! <= 0) { _widgetCounts.remove(widgetName); } }
static void printReport() { print('=== Widget Memory Report ==='); _widgetCounts.forEach((widget, count) { if (count > 10) { // Threshold for potential leaks print('WARNING: $widget has $count instances (potential leak)'); } else { print('$widget: $count instances'); } }); }}
// Use in your widgetsclass LeakAwareWidget extends StatefulWidget { @override State<LeakAwareWidget> createState() => _LeakAwareWidgetState();}
class _LeakAwareWidgetState extends State<LeakAwareWidget> { @override void initState() { super.initState(); MemoryLeakDetector.registerWidget('LeakAwareWidget'); }
@override void dispose() { MemoryLeakDetector.unregisterWidget('LeakAwareWidget'); super.dispose(); }
@override Widget build(BuildContext context) { return Container(); }}
Error Recovery
Automatic Error Recovery
Implement robust error recovery:
class ErrorRecoveryService { final InstantDB db; int _retryCount = 0; static const int _maxRetries = 3; static const Duration _retryDelay = Duration(seconds: 2);
ErrorRecoveryService(this.db);
Future<T> withRetry<T>( String operation, Future<T> Function() action, ) async { _retryCount = 0;
while (_retryCount < _maxRetries) { try { final result = await action(); _retryCount = 0; // Reset on success return result; } catch (e) { _retryCount++;
print('$operation failed (attempt $_retryCount/$_maxRetries): $e');
if (_retryCount >= _maxRetries) { print('$operation failed permanently after $_maxRetries attempts'); rethrow; }
// Wait before retry await Future.delayed(_retryDelay * _retryCount);
// Try to recover based on error type await _attemptRecovery(e); } }
throw Exception('Maximum retries exceeded for $operation'); }
Future<void> _attemptRecovery(dynamic error) async { if (error is InstantException) { switch (error.code) { case 'network_error': // Try to reconnect try { await db.syncEngine?.connect(); } catch (_) {} break; case 'auth_error': // Try to refresh auth try { await db.auth.refreshUser(); } catch (_) {} break; } } }}
Best Practices for Debugging
1. Enable Verbose Logging in Development
InstantDB.init( appId: 'your-app-id', config: InstantConfig( syncEnabled: true, verboseLogging: kDebugMode, // Only in debug mode ),);
2. Use Structured Error Handling
Future<void> handleOperation(Future<void> Function() operation) async { try { await operation(); } on InstantException catch (e) { // Handle InstantDB specific errors print('InstantDB Error: ${e.code} - ${e.message}'); _showUserFriendlyError(e); } catch (e, stackTrace) { // Handle other errors print('Unexpected error: $e'); print('Stack trace: $stackTrace'); _reportError(e, stackTrace); }}
3. Create Debug Screens
class DebugScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Debug Tools')), body: ListView( children: [ ListTile( title: const Text('Sync Diagnostics'), onTap: () => Navigator.push( context, MaterialPageRoute(builder: (_) => SyncDiagnostics()), ), ), ListTile( title: const Text('Database Inspector'), onTap: () => Navigator.push( context, MaterialPageRoute(builder: (_) => DatabaseInspector()), ), ), ListTile( title: const Text('Auth Debug'), onTap: () => Navigator.push( context, MaterialPageRoute(builder: (_) => AuthDebugScreen()), ), ), ], ), ); }}
4. Monitor App State
class AppStateMonitor extends StatefulWidget { final Widget child;
const AppStateMonitor({super.key, required this.child});
@override State<AppStateMonitor> createState() => _AppStateMonitorState();}
class _AppStateMonitorState extends State<AppStateMonitor> with WidgetsBindingObserver {
@override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); }
@override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); }
@override void didChangeAppLifecycleState(AppLifecycleState state) { print('App lifecycle state changed: $state');
final db = InstantProvider.of(context);
switch (state) { case AppLifecycleState.paused: // App is paused - might want to pause sync break; case AppLifecycleState.resumed: // App is resumed - ensure connection is active db.syncEngine?.connect(); break; case AppLifecycleState.detached: // App is being terminated break; default: break; } }
@override Widget build(BuildContext context) { return widget.child; }}
Getting Help
1. Collect Debug Information
Before asking for help, collect this information:
void collectDebugInfo() { print('=== InstantDB Debug Info ==='); print('App ID: ${db.appId}'); print('Sync enabled: ${db.config.syncEnabled}'); print('Is authenticated: ${db.auth.isAuthenticated}'); print('Current user: ${db.auth.currentUser.value?.email}'); print('Connection status: ${db.syncEngine?.connectionStatus.value}'); print('Flutter version: ${Platform.version}'); print('Platform: ${Platform.operatingSystem}');}
2. Reproduce Issues
Create minimal reproduction cases:
Future<void> reproduceIssue() async { // Minimal code that reproduces the problem final db = await InstantDB.init(appId: 'your-app-id');
try { // Steps to reproduce await db.transact([...db.create('test', {'id': db.id()})]); } catch (e) { print('Issue reproduced: $e'); }}
3. Report Bugs
Include this information when reporting bugs:
- InstantDB Flutter version
- Flutter/Dart version
- Platform (iOS/Android/Web)
- Complete error messages and stack traces
- Steps to reproduce
- Expected vs actual behavior
Next Steps
Learn more about maintaining robust InstantDB applications:
- Migration Strategies - Handling app updates and migrations
- Performance Optimization - Preventing performance issues
- Offline Functionality - Debugging offline scenarios
- API Reference - Complete API documentation