Building a WebSocket Client in Flutter: From Zero to Hero

Building a WebSocket Client in Flutter: From Zero to Hero

TB

Teqani Blogs

Writer at Teqani

May 21, 20254 min read

WebSockets are essential for real-time communication in applications like chat apps, live dashboards, games, or collaborative tools. Flutter, a powerful UI toolkit, simplifies creating such reactive applications. This guide offers a step-by-step approach to building a resilient WebSocket client in Flutter, covering everything from basic connectivity to advanced features.

Step 1: Set Up Your Flutter Project

Begin by setting up your Flutter project and adding the necessary dependencies. Add the following to your pubspec.yaml file:

dependencies:
 flutter:
 sdk: flutter
 web_socket_channel: ^2.3.0
 rxdart: ^0.27.4

Step 2: Basic WebSocket Connection

Let's start with a minimal WebSocket client to connect and receive messages. Here's a simple example:

import 'package:web_socket_channel/web_socket_channel.dart';

final channel = WebSocketChannel.connect(
 Uri.parse('wss://echo.websocket.events'), // Public echo server
);

channel.sink.add('Hello WebSocket!');

channel.stream.listen((message) {
 print('Received: $message');
});

Step 3: Create a Robust WebSocket Client

Now, let's create a reusable service with auto-reconnect, stream handling, and safe disconnection. Below is an example websocket_client.dart file:

import 'dart:async';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as status;
import 'package:rxdart/rxdart.dart';

class WebSocketClient {
 final String url;
 WebSocketChannel? _channel;
 final BehaviorSubject<String> _messages = BehaviorSubject<String>();
 Stream<String> get messages => _messages.stream;
 Timer? _reconnectTimer;
 int _retrySeconds = 1;
 bool _manuallyDisconnected = false;

 WebSocketClient(this.url);

 void connect() {
 _manuallyDisconnected = false;
 _channel = WebSocketChannel.connect(Uri.parse(url));
 _channel!.stream.listen(
 (data) {
 _messages.add(data);
 },
 onDone: _handleDisconnect,
 onError: (error) => _handleDisconnect(),
 );
 print('Connected to $url');
 }

 void _handleDisconnect() {
 if (_manuallyDisconnected) return;
 _channel = null;
 _scheduleReconnect();
 }

 void _scheduleReconnect() {
 _reconnectTimer?.cancel();
 _reconnectTimer = Timer(Duration(seconds: _retrySeconds), () {
 connect();
 _retrySeconds = (_retrySeconds * 2).clamp(1, 64); // exponential backoff
 });
 print('Reconnecting in $_retrySeconds seconds...');
 }

 void send(String message) {
 if (_channel != null) {
 _channel!.sink.add(message);
 }
 }

 void disconnect() {
 _manuallyDisconnected = true;
 _reconnectTimer?.cancel();
 _channel?.sink.close(status.goingAway);
 _messages.close();
 }
}

Step 4: Flutter UI to Test WebSocket

Let’s build a simple UI to send and receive messages via our client. Here’s an example main.dart file:

import 'package:flutter/material.dart';
import 'websocket_client.dart';

void main() {
 runApp(MyApp());
}

class MyApp extends StatelessWidget {
 final WebSocketClient client = WebSocketClient('wss://echo.websocket.events');
 @override
 Widget build(BuildContext context) {
 client.connect();
 return MaterialApp(
 home: WebSocketScreen(client: client),
 );
 }
}

class WebSocketScreen extends StatefulWidget {
 final WebSocketClient client;
 WebSocketScreen({required this.client});
 @override
 _WebSocketScreenState createState() => _WebSocketScreenState();
}

class _WebSocketScreenState extends State<WebSocketScreen> {
 final TextEditingController _controller = TextEditingController();
 @override
 void dispose() {
 widget.client.disconnect();
 super.dispose();
 }

 void _sendMessage() {
 final message = _controller.text;
 if (message.isNotEmpty) {
 widget.client.send(message);
 _controller.clear();
 }
 }

 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(title: Text('WebSocket Client')),
 body: Column(
 children: [
 Expanded(
 child: StreamBuilder<String>(
 stream: widget.client.messages,
 builder: (context, snapshot) {
 if (snapshot.hasData) {
 return Center(child: Text('Message: ${snapshot.data}'));
 } else {
 return Center(child: Text('Waiting for messages...'));
 }
 },
 ),
 ),
 Padding(
 padding: EdgeInsets.all(8.0),
 child: Row(
 children: [
 Expanded(child: TextField(controller: _controller)),
 IconButton(
 icon: Icon(Icons.send),
 onPressed: _sendMessage,
 )
 ],
 ),
 )
 ],
 ),
 );
 }
}

Step 5: Run the App

Run the app and:

  • Send a message
  • Receive the echoed message
  • Disable internet to simulate reconnection
  • Re-enable internet and observe automatic reconnect

Bonus Tips for Production

  • Secure your WebSocket endpoint with authentication (Firebase tokens, JWT, etc.)
  • Use SSL/TLS (wss://) in production
  • Add ping/pong or heartbeat messages for liveness detection
  • Centralize reconnection logic for better state management

Conclusion

With this robust WebSocket client setup in Flutter, you’ve gone from zero to hero. You’ve built:

  • A full connection lifecycle manager
  • Automatic reconnect with exponential backoff
  • Clean UI integration with real-time updates

Whether you’re building a real-time dashboard, live chat, or trading platform, this approach is scalable and production-ready.

TB

Teqani Blogs

Verified
Writer at Teqani

Senior Software Engineer with 10 years of experience

May 21, 2025
Teqani Certified

All blogs are certified by our company and reviewed by our specialists
Issue Number: #72ab3384-24f6-4114-91d2-4adb9b99315f