If you’ve ever tried to build a chat app, a live sports ticker, or a collaborative editing tool, you know that standard HTTP requests just don’t cut it. Waiting for a client to ‘poll’ a server every few seconds is inefficient and creates a laggy user experience. That’s where WebSockets come in. In this flutter web sockets tutorial, I’ll show you how to establish a persistent, bidirectional connection between your Flutter app and a server.
In my experience building real-time dashboards, WebSockets are the gold standard for reducing latency. Unlike REST, where the client must always initiate the conversation, WebSockets allow the server to push data to the client the millisecond it becomes available. If you’re looking for a more managed backend approach, you might want to check out my supabase flutter tutorial for beginners, but for raw control, the web_socket_channel package is your best friend.
Prerequisites
- Basic knowledge of Flutter and Dart (specifically Streams and StreamBuilders).
- A Flutter environment set up on your machine.
- A WebSocket server URL (I’ll use a public echo server for this tutorial, but in production, you’d use something like Serverpod or Node.js).
Step 1: Adding the Dependency
To get started, we need the web_socket_channel package. This is the community-standard wrapper that makes working with sockets in Dart much more intuitive.
flutter pub add web_socket_channel
Step 2: Connecting to the WebSocket Server
The core of a WebSocket implementation is the IOWebSocketChannel. I prefer creating a dedicated service class to handle the connection logic so it doesn’t clutter the UI code.
import 'package:web_socket_channel/web_socket_channel.dart';
class WebSocketService {
late WebSocketChannel channel;
void connect() {
// We are using a public echo server for testing
channel = WebSocketChannel.connect(
Uri.parse('wss://echo.websocket.events'),
);
print('Connected to WebSocket server');
}
void sendMessage(String message) {
channel.sink.add(message);
}
Stream get stream => channel.stream;
void disconnect() {
channel.sink.close();
}
}
Step 3: Implementing the UI with StreamBuilder
Because WebSockets are asynchronous streams of data, the StreamBuilder widget is the most efficient way to update your Flutter UI. As shown in the image below, we want the UI to react instantly whenever the server pushes a new message.
class ChatScreen extends StatefulWidget {
@override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final WebSocketService _wsService = WebSocketService();
final TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
_wsService.connect();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Real-time Flutter Chat')),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Expanded(
child: StreamBuilder(
stream: _wsService.stream,
builder: (context, snapshot) {
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
if (!snapshot.hasData) return Text('Waiting for messages...');
return Text('Server says: ${snapshot.data}');
},
),
),
TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Send a message'),
),
ElevatedButton(
onPressed: () {
_wsService.sendMessage(_controller.text);
_controller.clear();
},
child: Text('Send'),
),
],
),
),
);
}
}
Pro Tips for Production WebSockets
Building a basic connection is easy, but keeping it stable in a production environment is where things get tricky. Here are a few things I’ve learned from my own projects:
- Heartbeats/Ping-Pong: Many servers will drop a connection if it’s idle. Implement a ‘heartbeat’ (a small packet sent every 30 seconds) to keep the connection alive.
- Auto-Reconnect: Mobile devices switch between Wi-Fi and LTE constantly. Use a package like
connectivity_plusto trigger a_wsService.connect()call whenever the network returns. - JSON Serialization: Don’t send raw strings. Use
jsonEncode()andjsonDecode()to send structured data objects. - Backend Choices: If you are debating between a heavy-duty backend and a lightweight one, read my serverpod vs dart frog review to see which fits your WebSocket needs better.
Troubleshooting Common Issues
| Issue | Probable Cause | Solution |
|---|---|---|
| Connection Refused | Wrong URL or Server Down | Ensure you are using wss:// for secure connections. |
| App crashes on hot reload | Multiple active connections | Close the sink in the dispose() method of your stateful widget. |
| Data not updating in UI | Stream not listened to | Ensure StreamBuilder is wrapped around the widget that needs the data. |
What’s Next?
Now that you’ve mastered the basics of this flutter web sockets tutorial, you can start implementing more complex features like room-based chatting or real-time collaboration. If you want to explore alternative real-time databases that handle the socket layer for you, I highly recommend checking out the Supabase Flutter guide.