Flutter InstantDB
Real-time

WebSocket Synchronization

Real-time data synchronization with Flutter InstantDB

Flutter InstantDB provides real-time synchronization across all connected clients using WebSocket connections. Changes are propagated instantly with conflict resolution and offline support.

Enabling Sync

Enable real-time synchronization during database initialization:

final db = await InstantDB.init(
  appId: 'your-app-id',
  config: const InstantConfig(
    syncEnabled: true,  // Enable real-time sync
    verboseLogging: false, // Set to true for detailed sync logs
  ),
);

Connection Status

Monitor the connection status to provide user feedback:

// Using a reactive widget
Watch((context) {
  final syncEngine = db.syncEngine;
  final isConnected = syncEngine?.connectionStatus.value ?? false;
  
  return Row(
    children: [
      Icon(
        isConnected ? Icons.cloud_done : Icons.cloud_off,
        color: isConnected ? Colors.green : Colors.grey,
      ),
      Text(isConnected ? 'Connected' : 'Offline'),
    ],
  );
});

// Or create a custom connection status widget
class ConnectionStatusIndicator extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final db = InstantProvider.of(context);
    
    return Watch((context) {
      final isOnline = db.syncEngine?.connectionStatus.value ?? false;
      
      return AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        decoration: BoxDecoration(
          color: isOnline ? Colors.green : Colors.orange,
          borderRadius: BorderRadius.circular(12),
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(
              isOnline ? Icons.wifi : Icons.wifi_off,
              size: 16,
              color: Colors.white,
            ),
            const SizedBox(width: 4),
            Text(
              isOnline ? 'Online' : 'Offline',
              style: const TextStyle(
                color: Colors.white,
                fontSize: 12,
                fontWeight: FontWeight.w500,
              ),
            ),
          ],
        ),
      );
    });
  }
}

Lifecycle status with connectionStatus

For the full connection lifecycle (not just a boolean), read db.connectionStatus — a ReadonlySignal<ConnectionStatus>. The ConnectionStatus enum has five values:

ValueMeaning
connectingSocket is connecting
openedSocket open but not yet authenticated (pre init-ok)
authenticatedSocket open and authenticated — fully online
closedSocket is closed
erroredSocket errored

"Online" corresponds to ConnectionStatus.authenticated.

Watch((context) {
  final status = db.connectionStatus.value;
  final online = status == ConnectionStatus.authenticated;
  return Text(online ? 'Online' : status.name);
});

ConnectionStateBuilder widget

ConnectionStateBuilder rebuilds with the full lifecycle status:

ConnectionStateBuilder(
  builder: (context, status) {
    switch (status) {
      case ConnectionStatus.authenticated:
        return const Text('Online');
      case ConnectionStatus.connecting:
      case ConnectionStatus.opened:
        return const Text('Connecting...');
      case ConnectionStatus.closed:
      case ConnectionStatus.errored:
        return const Text('Offline');
    }
  },
)

Stable local id

db.getLocalId(name) returns a stable id for name that is persisted and survives restarts (matching useLocalId in the React SDK):

final sessionId = await db.getLocalId('session');

Deprecations: db.isOnline is deprecated — use connectionStatus (online == ConnectionStatus.authenticated). db.getAnonymousUserId() is deprecated — use getLocalId(name). Both still work for now.

How Sync Works

Differential Sync

InstantDB uses differential synchronization to efficiently sync only the changes:

  1. Local Changes: When you make local changes, they're applied immediately (optimistic updates)
  2. Sync Queue: Changes are queued for synchronization with the server
  3. WebSocket Transmission: Changes are sent via WebSocket in real-time
  4. Server Processing: Server processes and broadcasts changes to other clients
  5. Conflict Resolution: Automatic handling of concurrent modifications

Transaction Integrity

All operations maintain transaction integrity during sync:

// This transaction will be synced atomically
await db.transact([
  ...db.create('posts', {
    'id': db.id(),
    'title': 'New Post',
    'authorId': userId,
  }),
  db.update(userId, {
    'postCount': {'$increment': 1},
  }),
]);

Sync Events

Monitor sync events for debugging or user feedback:

// Listen to sync engine events (if available)
db.syncEngine?.onConnectionChange.listen((isConnected) {
  print('Connection status changed: $isConnected');
  
  if (isConnected) {
    // Connection restored - sync pending changes
    showSnackBar('Connection restored');
  } else {
    // Connection lost - work offline
    showSnackBar('Working offline');
  }
});

Handling Offline/Online Transitions

InstantDB handles offline scenarios gracefully:

Offline Behavior

When offline, InstantDB:

  • Stores all changes locally in SQLite
  • Continues to serve queries from local data
  • Queues mutations for later synchronization
  • Provides immediate UI updates (optimistic)

Coming Back Online

When connection is restored:

  • Pending transactions are automatically synced
  • Server changes are downloaded and applied
  • Conflicts are resolved automatically
  • UI updates reflect the synchronized state
// Example of handling online/offline states
class OfflineAwareWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final db = InstantProvider.of(context);
    
    return Watch((context) {
      final isOnline = db.syncEngine?.connectionStatus.value ?? false;
      
      return Column(
        children: [
          if (!isOnline)
            Container(
              width: double.infinity,
              padding: const EdgeInsets.all(8),
              color: Colors.orange,
              child: const Text(
                'Working offline - changes will sync when connected',
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.white),
              ),
            ),
          
          // Your main content
          InstantBuilder(
            query: {'todos': {}},
            builder: (context, result) {
              // Data is always available, even offline
              return TodoList(todos: result.data!['todos']);
            },
          ),
        ],
      );
    });
  }
}

Sync Performance

Batching Operations

For better performance, batch multiple operations:

// Instead of multiple separate transactions
await db.transact([...db.create('item1', data1)]);
await db.transact([...db.create('item2', data2)]);
await db.transact([...db.create('item3', data3)]);

// Use a single batched transaction
await db.transact([
  ...db.create('item1', data1),
  ...db.create('item2', data2),
  ...db.create('item3', data3),
]);

Optimistic Updates

InstantDB provides optimistic updates by default:

// UI updates immediately, then syncs in background
await db.transact([
  db.update(todoId, {'completed': true}),
]);

// The UI shows the change instantly, sync happens asynchronously

Advanced Sync Configuration

Custom Sync Settings

Configure sync behavior during initialization:

final db = await InstantDB.init(
  appId: 'your-app-id',
  config: const InstantConfig(
    syncEnabled: true,
    
    // Custom WebSocket URL (optional)
    websocketUrl: 'wss://custom.instantdb.com/ws',
    
    // Enable detailed logging for debugging
    verboseLogging: true,
    
    // Custom API endpoint
    baseUrl: 'https://custom.instantdb.com',
  ),
);

Sync Debugging

Enable verbose logging to debug sync issues:

final db = await InstantDB.init(
  appId: 'your-app-id',
  config: const InstantConfig(
    syncEnabled: true,
    verboseLogging: true, // Detailed sync logs
  ),
);

This will log:

  • WebSocket connection events
  • Transaction synchronization
  • Conflict resolution
  • Network errors and retries

Best Practices

1. Handle Connection States

Always provide feedback for offline states:

// Good: Show connection status
if (!isOnline) {
  return OfflineBanner();
}

// Bad: No indication of offline state

2. Optimistic UI Updates

Trust InstantDB's optimistic updates:

// Good: Update immediately, let InstantDB handle sync
await db.transact([db.update(id, newData)]);

// Bad: Wait for server confirmation

3. Batch Operations

Group related operations into single transactions:

// Good: Atomic operation
await db.transact([
  ...db.create('post', postData),
  db.update(userId, {'postCount': {'$increment': 1}}),
]);

// Bad: Separate operations that could fail independently

4. Monitor Connection

Provide connection status in your UI:

// Always show connection status in critical apps
AppBar(
  actions: [
    ConnectionStatusIndicator(),
  ],
)

Troubleshooting Sync Issues

Common Issues

  1. Not Syncing: Check if syncEnabled: true in config
  2. Slow Sync: Check network connection and server status
  3. Conflicts: Review conflict resolution logs
  4. Memory Issues: Ensure proper disposal of database instances

Debug Logging

Enable verbose logging to diagnose issues:

// Add this to see detailed sync logs
config: const InstantConfig(
  syncEnabled: true,
  verboseLogging: true,
),

Network Debugging

Test sync with network conditions:

// Simulate offline mode for testing
db.syncEngine?.disconnect();

// Reconnect
db.syncEngine?.connect();

Next Steps

Learn more about InstantDB's real-time features:

On this page