Two-Way, Real-Time Communication with WebSockets in Flutter Apps (+ Node backend Implementation)
Tags: node, dart, ios, android
Published: 2020-03-05 18:06:03

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](https://carmine.dev/posts/jwtauth/). As always, this is meant for people with a decent understanding of the basics of Flutter. I've [written a book about Flutter](https://pragprog.com/book/czflutr/programming-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 `Stream`s 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 possible. This is of particular importance to web developers, whose apps may run on browsers that don't support the WebSocket API, even though this is less and less of a concern as time passes. 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

If `channel` is the `WebSocketChannel`, you can close the connection using`channel.sink.close(statusCode);`. A list of status codes is available in `web_socket_channel`'s `status.dart`:

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

## Building an Example App

You can find the complete source code for this app [on this GitHub repository](https://github.com/carzacc/websockets_flutter).

Let's start with the `pubspec.yaml`, which needs to have as a dependency the `web_socket_channel` package:

{% gist https://gist.github.com/carzacc/ad6c0b8a2b04788423b51d0ab729287f file=pubspec.yaml %}

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`:

{% gist https://gist.github.com/carzacc/ad6c0b8a2b04788423b51d0ab729287f file=base.dart %}

### 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 `TextField`s (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:

{% gist https://gist.github.com/carzacc/ad6c0b8a2b04788423b51d0ab729287f file=fauxloginpage.dart %}

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:

{% gist https://gist.github.com/carzacc/ad6c0b8a2b04788423b51d0ab729287f file=homepage.dart %}

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

{% gist https://gist.github.com/carzacc/ad6c0b8a2b04788423b51d0ab729287f file=main.dart %}

## Building the Backend for the App

We're going to build the backend for the app with Node.js and the [`ws` npm package](https://www.npmjs.com/package/ws).

### The ws Package

There's a very popular and easy-to-use WebSocket client/server [package for Node called simply *ws*](https://www.npmjs.com/package/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](https://github.com/carzacc/websocketsbackend).

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

{% gist https://gist.github.com/carzacc/430d50943715dfbde2bd2082992af9a3 file=imports.js %}

Then set a port and start the WebSocket server:

{% gist https://gist.github.com/carzacc/430d50943715dfbde2bd2082992af9a3 file=startserver.js %}

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

{% gist https://gist.github.com/carzacc/430d50943715dfbde2bd2082992af9a3 file=letmsg.js %}

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:

{% gist https://gist.github.com/carzacc/430d50943715dfbde2bd2082992af9a3 file=connection.js %}

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

{% gist https://gist.github.com/carzacc/430d50943715dfbde2bd2082992af9a3 file=onmessage.js %}

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

{% gist https://gist.github.com/carzacc/430d50943715dfbde2bd2082992af9a3 file=broadcast.js %}

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

{% gist https://gist.github.com/carzacc/430d50943715dfbde2bd2082992af9a3 file=index.js %}

As always, if you enjoyed this post, consider [following me on Twitter @carminezacc](https://twitter.com/carminezacc).