Flutter WebSockets
Real-time bidirectional communication
⚡ What are WebSockets?
WebSockets enable real-time, two-way communication between client and server. Unlike HTTP, WebSockets maintain a persistent connection, perfect for chat apps, live updates, and real-time notifications without constant polling.
// Connect to WebSocket
final channel = WebSocketChannel.connect(
Uri.parse('wss://echo.websocket.org')
);
// Send message
channel.sink.add('Hello!');
Output:
✓ Connected to WebSocket
→ Sent: Hello!
WebSocket Features
Persistent Connection
Stay connected continuously
final channel = WebSocketChannel.connect(
Uri.parse('wss://server.com')
);
Send Messages
Push data to server
channel.sink.add(
json.encode({'type': 'message'})
);
Receive Messages
Listen for server updates
channel.stream.listen((message) {
print('Received: $message');
});
Close Connection
Properly disconnect
channel.sink.close();
🔹 Setting Up WebSockets
Add the web_socket_channel package:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
web_socket_channel: ^2.4.0
When to Use WebSockets:
- Chat Applications: Real-time messaging
- Live Updates: Stock prices, sports scores
- Notifications: Instant push notifications
- Collaborative Tools: Multi-user editing
🔹 Basic WebSocket Connection
Connect and communicate with a WebSocket server:
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:flutter/material.dart';
class WebSocketDemo extends StatefulWidget {
@override
_WebSocketDemoState createState() => _WebSocketDemoState();
}
class _WebSocketDemoState extends State<WebSocketDemo> {
final channel = WebSocketChannel.connect(
Uri.parse('wss://echo.websocket.org'),
);
final TextEditingController _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('WebSocket Demo')),
body: Padding(
padding: EdgeInsets.all(20),
child: Column(
children: [
TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Send a message'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
if (_controller.text.isNotEmpty) {
channel.sink.add(_controller.text);
}
},
child: Text('Send'),
),
SizedBox(height: 20),
StreamBuilder(
stream: channel.stream,
builder: (context, snapshot) {
return Text(
snapshot.hasData ? 'Received: ${snapshot.data}' : 'No data',
);
},
),
],
),
),
);
}
@override
void dispose() {
channel.sink.close();
_controller.dispose();
super.dispose();
}
}
Output:
[Input field: "Hello WebSocket"]
[Send Button]
Received: Hello WebSocket
🔹 Chat Application Example
Build a simple real-time chat:
import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';
class ChatService {
late WebSocketChannel channel;
final List<String> messages = [];
void connect(String username) {
channel = WebSocketChannel.connect(
Uri.parse('wss://your-chat-server.com/ws'),
);
// Listen for messages
channel.stream.listen(
(message) {
final data = json.decode(message);
messages.add('${data['user']}: ${data['text']}');
},
onError: (error) {
print('WebSocket error: $error');
},
onDone: () {
print('WebSocket connection closed');
},
);
// Send join message
sendMessage('joined the chat', username);
}
void sendMessage(String text, String username) {
final message = json.encode({
'user': username,
'text': text,
'timestamp': DateTime.now().toIso8601String(),
});
channel.sink.add(message);
}
void disconnect() {
channel.sink.close();
}
}
// Usage in Widget
class ChatScreen extends StatefulWidget {
@override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final ChatService _chatService = ChatService();
final TextEditingController _controller = TextEditingController();
final String username = 'User123';
@override
void initState() {
super.initState();
_chatService.connect(username);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Chat Room')),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _chatService.messages.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_chatService.messages[index]),
);
},
),
),
Padding(
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(hintText: 'Type a message'),
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: () {
if (_controller.text.isNotEmpty) {
_chatService.sendMessage(_controller.text, username);
_controller.clear();
}
},
),
],
),
),
],
),
);
}
@override
void dispose() {
_chatService.disconnect();
_controller.dispose();
super.dispose();
}
}
Output:
User123: joined the chat
Alice: Hello everyone!
User123: Hi Alice!
[Type a message...] [Send]
🔹 Reconnection Logic
Handle connection drops automatically:
class ReconnectingWebSocket {
WebSocketChannel? channel;
final String url;
bool _isConnecting = false;
int _reconnectAttempts = 0;
final int maxReconnectAttempts = 5;
ReconnectingWebSocket(this.url);
void connect() {
if (_isConnecting) return;
_isConnecting = true;
try {
channel = WebSocketChannel.connect(Uri.parse(url));
_reconnectAttempts = 0;
print('WebSocket connected');
channel!.stream.listen(
(message) {
print('Received: $message');
},
onError: (error) {
print('Error: $error');
_reconnect();
},
onDone: () {
print('Connection closed');
_reconnect();
},
);
} catch (e) {
print('Connection failed: $e');
_reconnect();
} finally {
_isConnecting = false;
}
}
void _reconnect() {
if (_reconnectAttempts >= maxReconnectAttempts) {
print('Max reconnection attempts reached');
return;
}
_reconnectAttempts++;
final delay = Duration(seconds: _reconnectAttempts * 2);
print('Reconnecting in ${delay.inSeconds} seconds...');
Future.delayed(delay, () {
connect();
});
}
void send(String message) {
channel?.sink.add(message);
}
void close() {
channel?.sink.close();
}
}
Console Output:
✓ WebSocket connected
⚠ Connection closed
Reconnecting in 2 seconds...
✓ WebSocket connected
🔹 Secure WebSocket (WSS)
Use secure WebSocket connections:
class SecureWebSocketService {
late WebSocketChannel channel;
void connect(String token) {
// Use wss:// for secure connection
channel = WebSocketChannel.connect(
Uri.parse('wss://secure-server.com/ws'),
);
// Send authentication token
channel.sink.add(json.encode({
'type': 'auth',
'token': token,
}));
channel.stream.listen(
(message) {
final data = json.decode(message);
if (data['type'] == 'auth_success') {
print('Authenticated successfully');
} else if (data['type'] == 'message') {
print('Message: ${data['content']}');
}
},
);
}
void sendSecureMessage(String message) {
channel.sink.add(json.encode({
'type': 'message',
'content': message,
'timestamp': DateTime.now().millisecondsSinceEpoch,
}));
}
}
Output:
✓ Authenticated successfully
Message: Welcome to secure chat!
🔹 Heartbeat / Ping-Pong
Keep connection alive with periodic pings:
import 'dart:async';
class WebSocketWithHeartbeat {
late WebSocketChannel channel;
Timer? _heartbeatTimer;
void connect() {
channel = WebSocketChannel.connect(
Uri.parse('wss://server.com/ws'),
);
// Start heartbeat
_startHeartbeat();
channel.stream.listen(
(message) {
if (message == 'pong') {
print('Heartbeat received');
} else {
print('Message: $message');
}
},
);
}
void _startHeartbeat() {
_heartbeatTimer = Timer.periodic(
Duration(seconds: 30),
(timer) {
channel.sink.add('ping');
print('Heartbeat sent');
},
);
}
void dispose() {
_heartbeatTimer?.cancel();
channel.sink.close();
}
}
Console Output:
Heartbeat sent
Heartbeat received
...30 seconds later...
Heartbeat sent