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

🧠 Test Your Knowledge

What is the main advantage of WebSockets over HTTP polling?