Using WebSockets and Real-Time Communication
1. Understanding WebSocket Protocol
| Aspect | Description | Details |
|---|---|---|
| Connection Type | Full-duplex communication | Allows simultaneous bidirectional data flow between client and server |
| Handshake | HTTP Upgrade Request | Initial HTTP request with headers like Sec-WebSocket-Key and Sec-WebSocket-Version |
| Protocol | WebSocket Subprotocol | Can define custom subprotocols for specific use cases (e.g., graphql-ws) |
| Message Types | Text and Binary | Supports both UTF-8 text messages and binary data (e.g., images, audio) |
| State Management | Open, Closing, Closed | WebSocket lifecycle includes OPEN, CLOSING, and CLOSED states |
Example: Basic WebSocket Connection Setup
import 'dart:html';
WebSocket webSocket = WebSocket('wss://example.com/websocket');
webSocket.onOpen.listen((event) {
print('WebSocket connected');
});
webSocket.onMessage.listen((event) {
print('Received: ${event.data}');
});
webSocket.onClose.listen((event) {
print('WebSocket closed');
});
Note: WebSocket connections are persistent and must be explicitly closed to free resources.
WebSocket Protocol Advantages
- Low latency communication
- Reduced overhead compared to HTTP polling
- Supports real-time updates and live data streams
- Used in chat apps, live dashboards, and collaborative tools
2. Connecting to WebSocket Server
| Property | Description | Example |
|---|---|---|
| WebSocket | Class for creating WebSocket connections | WebSocket ws = WebSocket('wss://example.com/ws'); |
| url | WebSocket server URL | WebSocket('wss://api.example.com/websocket'); |
| protocols | Optional list of sub-protocols | WebSocket('ws://localhost:8080', protocols: ['v1', 'v2']); |
Example: Basic WebSocket Connection
import 'dart:html';
WebSocket ws = WebSocket('wss://echo.websocket.org');
ws.onOpen.listen((event) {
print('Connected to WebSocket');
});
ws.onMessage.listen((event) {
print('Received: ${event.data}');
});
Note: Use
wss:// for secure connections and ws:// for non-secure.
Warning: Ensure the WebSocket server supports the
ws:// or wss:// protocol.
Connection Steps
- Create a
WebSocketinstance with the server URL - Listen to
onOpenfor successful connection - Handle messages via
onMessage - Manage connection lifecycle with
close()
3. Sending WebSocket Messages
| Method | Description | Parameters | Notes |
|---|---|---|---|
| WebSocket.send() | Sends a message over the WebSocket connection | message: String or Uint8List |
Must be called after the connection is open. Throws if not connected. |
| WebSocket.add() | Alias for WebSocket.send() |
message: String or Uint8List |
Same behavior as send() |
Example: Sending a Text Message
final socket = WebSocket('wss://example.com/websocket');
socket.add('Hello, server!');
Note: Ensure the WebSocket is in the
open state before sending messages. Use socket.readyState to check connection status.
4. Receiving WebSocket Messages
| Method | Description | Parameters | Notes |
|---|---|---|---|
| onMessage | Callback for handling incoming WebSocket messages | WebSocketChannel channel, dynamic message |
Triggered when a message is received from the server |
| onError | Callback for handling WebSocket errors | WebSocketChannel channel, dynamic error |
Invoked when an error occurs during message reception |
| onDone | Callback when the WebSocket connection closes | WebSocketChannel channel |
Executed once the connection is closed gracefully |
Example: Receiving WebSocket Messages
import 'package:web_socket_channel/web_socket_channel.dart';
void main() {
final channel = WebSocketChannel.connect(
Uri.parse('ws://localhost:8080/websocket'),
);
channel.stream.listen(
(message) {
print('Received: $message');
},
onError: (error) {
print('WebSocket error: $error');
},
onDone: () {
print('WebSocket connection closed');
},
);
}
Note: Ensure that the WebSocket server is running and accessible before attempting to receive messages.
Key Points
- onMessage is the primary handler for incoming messages.
- onError should be used to handle any connection or parsing errors.
- onDone is useful for cleanup or notifying the UI of a closed connection.
5. Handling WebSocket Errors
| Error Type | Description | Handling Strategy |
|---|---|---|
| WebSocketException | Thrown when a WebSocket connection fails or encounters an error during communication. | Use try-catch blocks to handle exceptions and provide fallback behavior or retry logic. |
| SocketException | Occurs when the socket connection is lost or cannot be established due to network issues. | Implement reconnection logic with exponential backoff and notify the user of connection status changes. |
| FormatException | Occurs when the received data cannot be parsed correctly (e.g., invalid JSON). | Validate incoming messages and handle parsing errors gracefully, logging the malformed data for debugging. |
Example: Handling WebSocket Errors in Flutter
import 'dart:io';
import 'package:web_socket_channel/web_socket_channel.dart';
void handleWebSocketErrors() {
final channel = WebSocketChannel.connect(
Uri.parse('ws://example.com/websocket'),
);
channel.stream.listen(
(message) {
try {
// Attempt to parse the message
final data = jsonDecode(message);
print('Received: $data');
} catch (e) {
print('Failed to parse message: $message');
}
},
onError: (error) {
if (error is SocketException) {
print('Network error occurred');
} else if (error is WebSocketException) {
print('WebSocket error: $error');
} else {
print('Unexpected error: $error');
}
},
onDone: () {
print('WebSocket connection closed');
},
);
}
Note: Always implement proper error handling for WebSocket connections to ensure robustness in real-time applications.
6. Closing WebSocket Connections
| Method | Description | Notes |
|---|---|---|
| WebSocket.close() | Initiates the closing handshake for a WebSocket connection | Gracefully closes the connection, sending a close frame to the server |
| WebSocket.close([code], [reason]) | Close with optional status code and reason | Code must be an integer between 1000-4999. Reason is a string explaining the closure |
Example: Closing a WebSocket Connection
final socket = WebSocket("wss://example.com/socket");
// ... perform operations ...
socket.close(1000, "Client initiated close");
Note: The
close() method is asynchronous and does not immediately terminate the connection. The actual closure happens after the close frame is sent and acknowledged.7. Implementing Reconnection Logic
Example: Basic WebSocket Reconnection
import 'dart:io';
import 'package:web_socket_channel/web_socket_channel.dart';
class WebSocketManager {
WebSocketChannel? channel;
String url = 'ws://example.com/websocket';
int maxRetries = 5;
int retryCount = 0;
Future<void> connect() async {
try {
channel = WebSocketChannel.connect(Uri.parse(url));
await channel!.stream.first;
retryCount = 0; // Reset on successful connection
} catch (e) {
if (retryCount < maxRetries) {
retryCount++;
await Future.delayed(Duration(seconds: 2 * retryCount));
await connect(); // Retry
}
}
}
void disconnect() {
channel?.sink.close();
}
}
| Property | Description | Default Value |
|---|---|---|
| maxRetries | Maximum number of reconnection attempts | 5 |
| retryCount | Current reconnection attempt count | 0 |
| url | WebSocket server URL to connect to | ws://example.com/websocket |
Note: Reconnection logic should handle network interruptions gracefully and avoid excessive retries that may overwhelm the server.
Reconnection Workflow
- Attempt to establish WebSocket connection
- If failed, increment retry count
- Wait with exponential backoff before retrying
- Repeat until max retries reached or connection established
8. Using Socket.IO Package
The socket_io_client package is the most popular choice for integrating Socket.IO functionality into Flutter applications. It provides a Dart implementation that closely mirrors the JavaScript Socket.IO client.
| Feature | Description | Notes |
|---|---|---|
| socket.connect() | Establishes a connection to the Socket.IO server | Must be called before emitting or listening to events |
| socket.emit() | Sends a message to the server | Can include data payload as second parameter |
| socket.on() | Registers a listener for an event | Callback receives data payload from server |
| socket.disconnect() | Closes the connection to the server | Recommended to call when no longer needed |
Example: Initializing Socket.IO Connection
import 'package:socket_io_client/socket_io_client.dart' as io;
void main() {
io.Socket socket = io.io('http://localhost:3000');
socket.connect();
}
Note: Ensure that your server is configured to accept connections from the Flutter app's origin, especially when running in a browser or mobile environment.
9. Implementing Real-Time Chat Features
Example: Basic WebSocket Connection Setup
import 'dart:io';
import 'package:web_socket_channel/web_socket_channel.dart';
void main() async {
final channel = WebSocketChannel.connect(
Uri.parse('ws://localhost:8080/chat'),
);
channel.stream.listen(
(message) {
print('Received: $message');
},
onError: (error) {
print('WebSocket error: $error');
},
onDone: () {
print('WebSocket closed');
},
);
}
| Property | Description | Values |
|---|---|---|
| WebSocketChannel.connect() | Creates a WebSocket connection to the specified URI | Uri |
| stream.listen() | Subscribes to messages from the WebSocket | onData, onError, onDone |
Example: Sending Messages to Server
void sendMessage(WebSocketChannel channel, String message) {
channel.sink.add(message);
}
// Usage
sendMessage(channel, 'Hello, Server!');
Note: Ensure the server supports WebSocket connections and is configured to handle real-time chat messages.
Key Steps for Real-Time Chat
- Establish a WebSocket connection to the chat server
- Listen for incoming messages using
stream.listen() - Send outgoing messages via
sink.add() - Handle connection errors and closure gracefully
10. Managing WebSocket State
| Property | Description | Values | Notes |
|---|---|---|---|
| WebSocketState | Represents the current state of a WebSocket connection | WebSocketState.connecting, WebSocketState.open, WebSocketState.closing, WebSocketState.closed |
Used to check the connection status in real-time |
Example: Checking WebSocket State
import 'dart:io';
final socket = await WebSocket.connect('wss://example.com/socket');
print(socket.readyState); // WebSocketState.open
// Listen to state changes
socket.listen(
(data) {
print("Received: $data");
},
onDone: () {
print("WebSocket closed");
},
);
// Check state before sending data
if (socket.readyState == WebSocketState.open) {
socket.add("Hello, server!");
}
Note: The
readyState property is read-only and reflects the current state of the WebSocket connection.