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

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'),
            ),
          ],
        ),
      ),
    );
  }
}
Flutter StreamBuilder implementation showing real-time data flow from WebSocket
Flutter StreamBuilder implementation showing real-time data flow from WebSocket

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:

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.