DEV Community

Cover image for Two-Way, Real-Time Communication with WebSockets in Flutter Apps (+ Node backend Implementation)
Carmine Zaccagnino
Carmine Zaccagnino

Posted on • Edited on

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 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 usingchannel.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:

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

Enter fullscreen mode Exit fullscreen mode

$ npm install ws


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

Enter fullscreen mode Exit fullscreen mode

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:{% raw %}

Enter fullscreen mode Exit fullscreen mode

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


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

Enter fullscreen mode Exit fullscreen mode

client.send(msg);


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

Enter fullscreen mode Exit fullscreen mode

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


An useful member of the {% raw %}`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:

Enter fullscreen mode Exit fullscreen mode

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 {% raw %}`index.js` is the following:

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

Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
nahuelcabrera profile image
Nahuel Cabrera

WHERE ARE U INITIALIZE SERVER?

Collapse
 
carminezacc profile image
Carmine Zaccagnino

Hmm, you're right, that gist isn't right. Check out the post on my blog carmine.dev/posts/flutterwebsockets/ which doesn't have the mistake, I'll fix it here ASAP

Collapse
 
sahilkachhap profile image
Sahil Kachhap

Can we send any kind of data through the websocket channel ?