Two-Way, Real-Time Communication with WebSockets in Flutter Apps (+ Node backend Implementation)

Hi everyone, in this post I’m going to show you how to use WebSockets in Flutter apps and write a Node backend to test the app.

In this post we’re not going to worry about authentication/authorization, as that was the focus of the previous post in the series. As always, this is meant for people with a decent understanding of the basics of Flutter. I’ve written a book about Flutter that will get you up to speed reasonably quickly and with ease but, in case you really need to do this now and either don’t like learning from the book or don’t want to read the whole thing, here’s the concepts I’m going to suppose you know how to use in order to follow this post:

  • basic structure of a Flutter app (MaterialApp, Scaffold, Column, definition of custom widgets etc.);
  • getting input from the user using a TextField and managing it with a TextEditingController;
  • basic navigation using Navigator.push;
  • asynchronous programming with Streams and the usage of the StreamBuilder.

WebSocket and Socket.io

This post is about WebSockets. It won’t be about Socket.IO, which might be the focus of another post. WebSocket is a protocol (just like HTTP) and there are some packages and libraries to use it directly, but a very popular alternative to doing that is using Socket.io, which is a library that may or may not use WebSocket as its communication protocol, given that it has its own real-time communication engine that is used in case there is no way to establish a WebSocket-based connection.

The way Socket.io does it is rather the other way around, using its own engine to initiate the connection, upgrading to WebSocket if it’s supported. The main difference you would notice in the example in a tutorial is that Socket.io supports server broadcasting by default, meaning you don’t have to manually iterate over the connected clients to send the message to each, as that is a feature of Socket.io itself.

What We’re Going to Build

A very common application for WebSockets is building a chat app. That’s a very good example but, in my view, it’s not a great example for a blog post, unless what one wants to teach is how to build a chat app and not how to use WebSockets. I used the example of a chat app in my book to show how to use Firebase, but that was to show as many aspects of Flutter and Firebase as possible in one example, and it is a cool example.

What I’m going to do in this post, though, is show you everything you need to know in order to build a real time app, leaving the rest to you and avoid showing how to interact with a specific database, how to build a very specific complex user interface: the example is simply going to be an app showing the latest message sent by an user as an announcement to every connected user.

WebSockets in Flutter

The web_socket_channel Dart WebSocket package is Google-developed and very easy to use. That’s what we’re going to use in this post.

Opening a Connection

A connection can be opened by creating an object of class WebSocketChannel, and you can connect to a WebSocket server by using the WebSocketChannel.connect contructor: channel = WebSocketChannel.connect(URI); where URI is an Uri, that you could get from a String containing an URL (something like ws://myserver.mydomain.com:3000) by using Uri.parse(URL).

Sending and Receiving Data

The WebSocketChannel.stream is used to listen for messages. As the name implies, it’s a Stream, which is exactly the best data type for incoming data from a WebSocket. It returns any new messages coming from the WebSocket as soon as they are received.

The WebSocketChannel.sink is used to send messages. As the name implies, it’s a StreamSink. If you’ve read my book or have worked with the Firebase Cloud Firestore before, this is used in a similar way to the Firestore’s CollectionReference object: WebSocketChannel.sink.add(data) sends data through the WebSocket.

Closing the Connection

channel.sink.close(statusCode);

  @override
  void dispose() {
    super.dispose();
    channel.sink.close(statusCodes.goingAway);
  }

Building an Example App

You can find the complete source code for this app in this GitHub repository.

Let’s start with the pubspec.yaml, which needs to install the web_socket_channel package:

In lib/main.dart we’re going to import package:web_socket_channel/web_socket_channel.dart to use the WebSocketChannel, then we set the server IP and port, and then start an app that has as its home page a class called FauxLoginPage:

The FauxLoginPage

The FauxLoginPage is going to be, as the name implies, a fake login page: it’s not going to be a proper login page for the user to authenticate, but just a page for the user to set an username. As I wrote above, we’re not going to worry about authentication because that was the focus of the previous post. If you know how to use TextFields (and especially if you’re familiar with the Firebase chat app example in my book, which has a login page that works a bit like this one but actually authenticates the user) this is all going to be self-explanatory and simple:

The AnnouncementPage is going to simply be a StatelessWidget: we’re going to let a StreamBuilder take care of the changes in values returned by the Stream of data from the WebSocket. Below the text coming from the WebSocket, we’re going to have a TextField that allows the user to send a message. We convert the data to a string so that it could technically be anything, and be shown to the user as is, to make debugging easier:

The entire main.dart is going to be the following, then:

Building the Backend for the App

We’re going to build the backend for the app with Node.js and the ws npm package.

The ws Package

There’s a very popular and easy-to-use WebSocket client/server package for Node called simply ws, so you can install it using

$ npm install ws

You can start a WebSocket server that listens to a given port with the following code:

var server = new WebSocket.Server(
  {
    port: port,
  }
);

you can wait for a connection and define a callback to be ran when a client connects with the following code:

server.on('connection', function connection(client) {
	// code to execute when a client connects
});

This gives us a client object we can use to send messages to the connected WebSocket client using client.send():

client.send(msg);

We can also listen for messages sent by the client over the WebSocket and run a function when that happens:

client.on('message', function incoming(message) {
	// code to execute when a message is received
});

An useful member of the server object is server.clients, which is an array of the connected clients. This means we can send a message to each connected client with the following:

for(var cl of server.clients) {
  cl.send(message);
}

Implementing the Backend

You can find source code for the backend at this GitHub repository.

The first thing we need is to import the ws package:

Then set a port and start the WebSocket server:

let’s also define a default message to send to the client the first time:

When a client connects for the first time, we send them that message so the client has something to display to the user when they connect:

Now, let’s handle the reception of a message:

What we should do is broadcast the received message to all connected clients:

If we also log to console each received message the final index.js is the following:

As always, if you enjoyed this post, consider following me on Twitter @carminezacc.