WebSocket Plugin
A robust Flutter WebSocket plugin utilizing the Adapter design pattern for flexible WebSocket communication. This plugin provides a modular, testable, and easy-to-use interface for WebSocket connections with support for automatic reconnection, different message types, comprehensive error handling, and enhanced resilience.
Features
- Adapter Design Pattern: Easy switching between different WebSocket implementations
- Automatic Reconnection: Configurable auto-reconnect with exponential backoff
- Multiple Message Types: Support for text, JSON, and binary messages
- Comprehensive State Management: Real-time connection state tracking
- Error Handling: Robust error handling and reporting
- Testing Support: Built-in mock adapter for unit testing
- Logging: Optional detailed logging for debugging
- Type Safety: Full TypeScript-like type safety with Dart
- Heartbeat Mechanism: Keep connections alive with customizable ping/pong cycles
- Server Inactivity Detection: Automatically detect when the server stops responding
- Missed Heartbeat Tracking: Monitor connection health with configurable thresholds
- Automatic Recovery: Trigger reconnection when heartbeat failures exceed limits
- Enhanced Reconnection: Intelligent retry delays that increase exponentially
- Jitter Support: Randomized delays to prevent thundering herd problems
- Maximum Delay Caps: Configurable upper limits for reconnection delays
- Connection Resilience: Robust handling of network instability
- Advanced Monitoring: Comprehensive connection and heartbeat metrics
- Health Monitoring: Track connection quality and performance
- Detailed Logging: Enhanced debugging with heartbeat and reconnection logs
Installation
Add this to your package's pubspec.yaml
file:
dependencies:
adapter_websocket: 0.0.1
Quick Start
Basic Usage
import 'package:websocket_plugin/websocket_plugin.dart';
// Create configuration
final config = WebSocketConfig(
url: 'wss://echo.websocket.org',
autoReconnect: true,
maxReconnectAttempts: 3,
enableLogging: true,
);
// Create adapter and client
final adapter = WebSocketChannelAdapter(config);
final client = WebSocketClient(adapter);
// Listen to messages
client.messageStream.listen((message) {
print('Received: ${message.data}');
});
// Connect and send messages
await client.connect();
await client.sendText('Hello, WebSocket!');
await client.sendJson({'type': 'greeting', 'message': 'Hello'});
Advanced Configuration
final config = WebSocketConfig(
url: 'wss://your-websocket-server.com',
protocols: ['chat', 'superchat'],
headers: {'Authorization': 'Bearer your-token'},
pingInterval: Duration(seconds: 30),
connectionTimeout: Duration(seconds: 10),
reconnectDelay: Duration(seconds: 5),
maxReconnectAttempts: 5,
autoReconnect: true,
enableLogging: true,
// Enhanced reconnection with exponential backoff
useExponentialBackoff: true,
maxReconnectDelay: Duration(minutes: 5),
backoffMultiplier: 2.0,
// Heartbeat configuration
enableHeartbeat: true,
heartbeatInterval: Duration(seconds: 30),
heartbeatTimeout: Duration(seconds: 10),
heartbeatMessage: 'ping',
expectedPongMessage: 'pong',
maxMissedHeartbeats: 3,
);
Architecture
Adapter Pattern Implementation
The plugin uses the Adapter design pattern to provide flexibility in WebSocket implementations:
// Abstract adapter interface
abstract class WebSocketAdapter {
Stream<WebSocketState> get stateStream;
Stream<WebSocketMessage> get messageStream;
Stream<dynamic> get errorStream;
Stream<Map<String, dynamic>> get statsStream;
Future<void> connect();
Future<void> sendMessage(WebSocketMessage message);
Future<void> disconnect([int? code, String? reason]);
Future<void> forceReconnect();
}
// Concrete implementation using web_socket_channel
class WebSocketChannelAdapter implements WebSocketAdapter {
// Implementation details...
}
// Mock implementation for testing
class MockWebSocketAdapter implements WebSocketAdapter {
// Mock implementation...
}
Key Components
- WebSocketClient: High-level client interface with auto-reconnection
- WebSocketAdapter: Abstract interface for different implementations
- WebSocketConfig: Configuration class for connection parameters
- WebSocketMessage: Typed message container with metadata
- WebSocketState: Enumeration of connection states
- WebSocketStats: Comprehensive statistics for connection and heartbeat health
Message Types
Text Messages
await client.sendText('Hello, World!');
JSON Messages
await client.sendJson({
'type': 'chat',
'message': 'Hello',
'timestamp': DateTime.now().toIso8601String(),
});
Binary Messages
await client.sendBinary([1, 2, 3, 4, 5]);
Custom Messages
final message = WebSocketMessage(
data: 'custom data',
timestamp: DateTime.now(),
type: 'custom',
metadata: {'priority': 'high'},
);
await client.sendMessage(message);
State Management
The plugin provides real-time state tracking:
client.stateStream.listen((state) {
switch (state) {
case WebSocketState.connecting:
print('Connecting...');
break;
case WebSocketState.connected:
print('Connected!');
break;
case WebSocketState.disconnecting:
print('Disconnecting...');
break;
case WebSocketState.disconnected:
print('Disconnected');
break;
case WebSocketState.error:
print('Connection error');
break;
}
});
Error Handling
Comprehensive error handling with detailed error streams:
client.errorStream.listen((error) {
print('WebSocket error: $error');
// Handle error appropriately
});
Testing
The plugin includes enhanced mock capabilities for testing heartbeat and reconnection scenarios:
import 'package:flutter_test/flutter_test.dart';
import 'package:websocket_plugin/websocket_plugin.dart';
void main() {
test('should send and receive messages', () async {
final config = WebSocketConfig(url: 'wss://test.example.com');
final mockAdapter = MockWebSocketAdapter(config);
final client = WebSocketClient(mockAdapter);
await client.connect();
await client.sendText('test message');
expect(mockAdapter.sentMessages.length, equals(1));
expect(mockAdapter.sentMessages.first.data, equals('test message'));
// Simulate receiving a message
mockAdapter.simulateTextMessage('response');
// Verify message was received
// ... test assertions
});
test('should handle heartbeat timeouts', () async {
final config = WebSocketConfig(
url: 'wss://test.com',
enableHeartbeat: true,
heartbeatInterval: Duration(milliseconds: 100),
maxMissedHeartbeats: 2,
);
final mockAdapter = MockWebSocketAdapter(config);
mockAdapter.setAutoRespondToPing(false); // Simulate unresponsive server
final client = WebSocketClient(mockAdapter);
// Monitor for reconnection attempts
bool reconnectionTriggered = false;
client.statsStream.listen((stats) {
if (stats['reconnection']['isReconnecting'] == true) {
reconnectionTriggered = true;
}
});
await client.connect();
await Future.delayed(Duration(milliseconds: 500));
expect(reconnectionTriggered, isTrue);
});
test('should simulate network instability', () async {
final mockAdapter = MockWebSocketAdapter(config);
mockAdapter.setSimulateUnstableConnection(true);
final client = WebSocketClient(mockAdapter);
await client.connect();
// Connection will randomly disconnect and reconnect
// Perfect for testing resilience
});
}
Logging
Enable detailed logging for debugging:
final config = WebSocketConfig(
url: 'wss://your-server.com',
enableLogging: true,
);
client.logStream.listen((log) {
print('WebSocket Log: $log');
});
Best Practices
- Always dispose clients: Call
client.dispose()
when done - Handle connection states: Listen to state changes for UI updates
- Implement error handling: Always listen to error streams
- Use appropriate message types: Choose the right message type for your data
- Configure timeouts: Set appropriate connection and reconnection timeouts
- Test with mocks: Use the mock adapter for unit testing
- Configure appropriate heartbeat intervals based on your network conditions
- Use exponential backoff for production environments
- Monitor connection statistics to optimize settings
- Test with mock adapter to verify resilience
- Enable logging during development for debugging
Examples
See the example/
directory for a complete Flutter app demonstrating all features of the WebSocket plugin.
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License
This project is licensed under the MIT License - see the LICENSE file for details.