Globally, there is an ever-increasing appetite for data delivered in real time. Both producers and consumers are becoming more and more interested in faster experiences and instantaneous data transactions, with WebSockets being probably the most popular protocol for such use cases.
WebSockets represent a long-awaited evolution in the client/server web technology. They are a highly efficient and low-latency mechanism that enables bi-directional, full-duplex messages to be instantly distributed, with low overhead. Before WebSockets came along, the realtime web existed, but it was difficult to achieve, typically slower, and was implemented by hacking web technologies that were not designed for realtime applications. The WebSocket protocol is the one that gave way to a truly realtime web and elevated the possibilities of communication over the Internet.
Released only a few years ago but quickly gaining in popularity, Flutter is an open-source UI software development kit that can be used to develop natively compiled applications (primarily iOS and Android ones) from a single code base. Most of the Flutter system is implemented in Dart, a fast-growing modern object-oriented language optimized for client apps.
Flutter has in-built support for WebSockets via the web_socket_channel package, enabling you to connect to a WebSocket server, listen to messages emitted by the server, and send data to the server.
Since demand for realtime data is growing steadily, and with Flutter being a popular UI framework for building iOS and Android apps, I think it’s worth looking at some of the many challenges of implementing a dependable client-side Flutter solution that uses WebSockets.
State of play — a brief overview
Rarely is a basic or raw WebSocket implementation enough for the needs of a realtime app that services an unknown (but potentially very high) number of users. Most of the time, you need to think about extending the capabilities of your Flutter client-side WebSocket implementation.
For this purpose, you could use an open-source solution like Websocket Manager, a Flutter plugin for building Android and iOS apps that use WebSockets. This is how you use Websocket Manager:
int messageNum = 0;
// Configure WebSocket url
final socket = WebsocketManager('wss://echo.websocket.org');
// Listen to close message
socket.onClose((dynamic message) {
print('close');
});
// Listen to server messages
socket.onMessage((dynamic message) {
print('recv: $message');
if messageNum == 10 {
socket.close();
} else {
messageNum += 1;
final String msg = '$messageNum: ${DateTime.now()}';
print('send: $msg');
socket.send(msg);
}
});
// Connect to server
socket.connect();
However, while it’s true that a solution like Websocket Manager offers some benefits (such as the ability to keep connections active in the background), it’s little more than a basic WebSocket client; it provides no rich additional functionality.
To get the most out of WebSockets, a protocol that’s built on top is often used, which enables richer functionality, such as pub/sub. You could choose to develop your own WebSocket-based protocol that is tailored specifically to your needs. However, that is a very complex and time-consuming undertaking. Most of the time, you are better off using an established WebSocket-based solution that is well prepared to handle an entire set of engineering complexities.
Here at Ably, we have a protocol for pub/sub that is used on top of WebSockets. It allows you to communicate over WebSockets by using a higher-level set of capabilities. To demonstrate how simple it is, here’s an example of how data is published to Ably channels:
// publish name and data
await channel.publish(name: "event1", data: "hello world");
await channel.publish(name: "event1", data: {"hello": "world", "hey": "ably"});
await channel.publish(name: "event1", data: [{"hello": {"world": true}, "ably": {"serious": "realtime"}]);
// publish single message
await channel.publish(message: ably.Message()..name = "event1"..data = {"hello": "world"});
// publish multiple messages
await channel.publish(messages: [
ably.Message()..name="event1"..data = {"hello": "ably"},
ably.Message()..name="event1"..data = {"hello": "world"}
]);
And here’s how clients subscribe to a channel to receive messages:
var messageStream = channel.subscribe();
var channelMessageSubscription = messageStream.listen((ably.Message message){
print("New message arrived ${message.data}");
});
As you can see from the code snippets above, although the communication is done over WebSockets, the underlying WebSocket protocol is ‘’hidden’’ from developers.
You can decide to use any WebSocket-based protocol that supports Flutter. Regardless of your choice, though, you need to consider the entire spectrum of challenges you’ll face when it comes to WebSockets.
WebSockets and Flutter: what you need to consider
Before we get started, I must emphasize that this article only focuses on the client-side challenges of building a dependable WebSocket-based solution for realtime Flutter apps. I assume you have already decided what solution you want to use on the server-side. If you haven’t yet, you can go with an open-source library like Socket.IO, or a cloud-based solution like Ably.
It’s also worth mentioning that I’ve written this article based on the extensive collective knowledge that the Ably team possesses about the challenges of realtime communication via WebSockets.
I’m now going to dive into the key things you need to think about, such as authentication, network compatibility, or handling reconnections with continuity. Your client-side WebSocket implementation must be equipped to efficiently handle all these complexities if you are to build a reliable system.
Do you actually need WebSockets?
WebSocket is the most popular and portable realtime protocol. It provides full-duplex communication over a single TCP connection. WebSockets are a great choice for many use cases, such as financial tickers, chat solutions, or location-based apps, to name just a few. But it’s not the only available option. Before jumping on the WebSocket bandwagon, you should look at the existing alternatives, to make sure they are not a better fit for your use case.
For example, MQTT, which also supports bidirectional communication, is the go-to protocol for IoT devices with limited battery life, and for networks with expensive or low bandwidth, unpredictable stability, or high latency. Another example is Server-Sent Events, a lightweight protocol from both an implementation and usage standpoint. It’s a superior alternative for most browser-based scenarios where unidirectional communication is enough, such as users subscribing to a basic news feed.
WebSockets, MQTT, and SSE are all TCP-based protocols. TCP is designed to be a reliable transport layer protocol, which provides message delivery and ordering guarantees. This is great for many realtime use cases. But for other use cases, a lightweight, speedier protocol is a better option. For example, if your purpose is to stream video data, you’re better off using UDP as your transport layer protocol.
Even if WebSocket is a good choice for your needs, depending on the complexity of your architecture and what you are trying to achieve with your system, you might want to have the flexibility of using multiple protocols, one for each specific use case.
How We Solve It
Ably and protocol interoperability
https://www.okbar.org/advert/ku-vs-usc-2021-live-kansas-vs-usc-live-stream-mens-basketball-free/
https://pactforanimals.org/advert/march-madness-2021-live-u-vs-usc-live-stream-mens-basketball-free/
https://pactforanimals.org/advert/march-madness-wyoming-vs-ucla-live-stream-womens-basketball-free/
https://www.okbar.org/advert/watch-free-ucla-vs-wyoming-2021-live-stream-womens-basketball-free/
https://pactforanimals.org/advert/watch-free-oregon-vs-south-dakota-live-stream-womens-basketball-free/
https://www.okbar.org/advert/march-madness-south-dakota-vs-oregon-live-stream-womens-basketball-free/
https://blog.goo.ne.jp/joliv/e/bfc686b7388826a0808ca83cd84719f9
https://www.deviantart.com/gdsffds/journal/Building-realtime-apps-with-Flutter-and-WebSocket-874025457
https://paiza.io/projects/AQsUyFVoIfyLdPUuC0f2Gg
https://caribbeanfever.com/photo/albums/flutter-and-websockets
https://www.thewyco.com/general/building-realtime-apps-with-flutter-and-websockets-client-side-considerations-22-03-2021
At Ably, we embrace open standards and interoperability, and we believe that you should have the flexibility to choose the right protocol for the job at any given moment. That’s why we support WebSockets, SSE and MQTT, among other options.
Learn more about the protocols Ably supports
Authentication
Generally, it’s a good idea to only allow authenticated users to use a WebSocket connection. However, a raw WebSocket request has no header, so you’re unable to provide authentication in the way you might with an HTTP request. That’s why you need to use another component or service for authentication.
A client-side WebSocket library might be an option, but be careful when selecting one, as not all of them provide authentication mechanisms (or if they do, they can be quite limited). Alternatively, you can build your own authentication service. Most of the time, though, you are better off using a mature, feature-rich solution, such as a realtime messaging platform, that handles not only authentication, but an entire set of engineering complexities related to streaming data over WebSockets.
Let’s now look at the most common authentication mechanisms you can use over WebSockets. The first one, basic authentication, is the simplest option available (and also the least secure) and it involves the use of API keys. The credentials are usually passed as a query parameter in a URL, which looks something like this:
wss://realtime.ably.com/?key=MY_API_KEY&format=json&heartbeats=true&v=1.1&lib=js-web-1.2.1
From a security perspective, it’s recommended to only use basic authentication server-side, because exposing an API key to multiple clients is highly insecure. In theory, if you use ephemeral API keys that expire after a given amount of time on the client-side, you minimize the risk of them being compromised. However, in practice, a URL containing an ephemeral API key would be broken as soon as the key expires. In addition, request logging services would capture the API key in server logs. Therefore, you would have to open a new WebSocket connection each time the API key is refreshed. This is not a scalable approach.
Token-based authentication is another widely-used authentication mechanism. One of the most popular methods of token-based authentication is called JSON Web Token (JWT). It’s an open and flexible format that has become an industry standard. At a basic level, JWTs work as illustrated in this diagram:
Websockets and iOS image 1
The client sends an authorization request to the authorization server.
The authorization server returns an access token to the client.
The client uses the access token to access a protected resource.
JWT is the recommended strategy on the client-side as it provides more fine-grained access control than basic authentication and limits the risk of exposed or compromised credentials. Furthermore, in addition to JWTs being ephemeral by design, they can also be revoked.
While JWTs are obviously more reliable and secure than basic authentication, they come with some engineering challenges that you will have to manage if you decide to develop your own JWT-based authentication solution:
How do you handle token privileges and permissions?
How do you set TTL (Time To Live)?
How do you renew tokens?
How are tokens sent? If it’s via URL, you need a mechanism that allows you to renew tokens when they expire, without starting a new WebSocket connection.
How We Solve It
See how Ably handles authentication
At Ably, we provide two types of authentication mechanisms: basic auth (API keys), which we recommend to be used exclusively on the server-side, and token-based authentication, which supports both Ably tokens and JWTs, and is ideal for the client-side. We handle all the complexity around renewing tokens and permission management through our client SDKs.
Learn more about Ably’s authentication mechanisms
Network compatibility and fallback transports
Despite widespread platform support, WebSockets suffer from some networking issues. The main one is proxy traversal. For example, some servers and corporate firewalls block WebSocket connections.
Ports 80 and 443 are the standard ports for web access and they support WebSockets. Note that port 80 is used for insecure connections. With that in mind, it’s recommended to use port 443 for WebSockets whenever possible, because it’s secure, and prevents proxies from inspecting the connections. If you have to (or wish to) run WebSockets on different ports, network configuration is usually needed.
That said, if you can’t use port 443 and you foresee clients connecting from within corporate firewalls or otherwise tricky sources, you might need to support fallback transports such as XHR streaming, XHR polling, or JSONP polling. That’s why you need to use a client-side WebSocket-based solution that supports fallbacks. This solution can take various forms; you can opt for an open-source library designed specifically to provide lots of fallback capabilities, such as SockJS.
Alternatively, you can build your own complex fallback capability, but the scenarios where you actually have to are very rare. Developing a custom solution is a complex process that takes a lot of time and effort. In most cases, to keep engineering complexity to a minimum, you are better off using an existing WebSocket-based solution that provides fallback options.
How We Solve It
Ably’s WebSocket-based protocol
Ably’s aim has always been to provide a reliable, scalable, and highly-performant realtime pub/sub messaging service. Because of our service aims, we wanted to provide guarantees around message ordering and delivery. The raw WebSocket transport doesn’t solve this out-of-the-box. That’s why we made the decision to build our own protocol on top of WebSockets, which also include fallbacks, among other features and benefits.
Learn more about:
the Ably protocol
the fallbacks we support via our Javascript browser library
Device power management and heartbeats
By its very nature, a WebSocket connection is persistent. This also means it’s consuming battery life for as long as it’s active. With WebSockets enjoying support across various development platforms, programming languages, and frameworks, including Flutter, device power management is something essential to consider. Unfortunately, it’s not handled by many Flutter WebSocket libraries.
There are several ways you can approach battery management and heartbeats. The WebSocket protocol natively supports control frames known as Ping and Pong. This is an application-level heartbeat mechanism that allows you to detect whether a WebSocket connection is alive. Usually, the server-side sends a Ping frame and, on receipt, the client-side sends a Pong frame back to the server.
You could theoretically also use protocol-level heartbeats — TCP keepalives. However, this is a less than ideal option, as TCP keepalives don’t get past web proxies. In other words, with TCP keepalives, you can’t always verify a connection end-to-end.
Be aware that the more heartbeats you send, the faster battery is depleted. This can be quite problematic, especially when mobile devices are involved. In these scenarios, to preserve battery life, you may want to use OS-native push notifications where possible. With this approach, there’s no need to maintain a WebSocket connection active, because the server can wake up an inactive instance of your app whenever you send a push notification.
While push notifications are a great choice for waking up an app, they are not a replacement for a WebSocket or streaming protocol layer, because they do not provide quality of service guarantees. Push notifications are typically ephemeral, have variable latencies, and aren’t ordered. Furthermore, there is usually a limited number of push notifications that can be queued up for delivery at any given moment.
Before deciding whether to use heartbeats, push notifications, or both, you must carefully analyze your system requirements and use cases. For example, if you’re developing a chat solution or a multiplayer game, you want to send frequent heartbeats, even if that means battery life is depleted faster. On the other hand, if you are developing an online shop and want to infrequently notify customers about new products, you might want to use push notifications for this specific purpose. Push notifications will better preserve battery life, even if it means that your connection status detection will not be as accurate.
How We Solve It
Ably, heartbeats and push notifications
At Ably, we believe that you should always have the flexibility to decide whether you want to use heartbeats, push notifications, or both, depending on the specifics of your use case(s). That’s why we provide a heartbeat functionality, as well as native push notifications.
Learn more about:
Ably’s native push notifications
Heartbeats and battery usage
Handling reconnections with continuity
It’s common for devices to experience changing network conditions. Devices might switch from a mobile data network to a Wi-Fi network, go through a tunnel, or perhaps experience intermittent network issues. Abrupt disconnection scenarios like these require a WebSocket connection to be re-established.
For some realtime use cases, resuming a stream after disruption precisely where it left off is essential. Think about features like live chat, where missing messages during disconnection cause confusion and frustration. You need to ensure that your client-side WebSocket implementation is equipped to handle complexities around reconnections. The implementation should also provide the means to resume a stream and offer exactly-once delivery guarantees.
If resuming a stream exactly where it left off after brief disconnections is important to your use case, you need to implement history / persisted data. Getting into the detail of this is out of the scope of this article (see this blog post for more), but these are some things you’ll need to consider:
Caching messages in front-end memory. How many messages do you store and for how long?
Moving data to persistent storage. Do you need to move data to disk? If so, how long do you store it for? Where do you store it? And how will clients access that data when they reconnect?
Resuming a stream. When a client reconnects, how do you know which stream to resume and where exactly to resume it from? Do you need to use a connection ID to establish where a connection broke off? Who needs to keep track of the connection breaking down - the client, or the server?
Backoff mechanism. What is your incremental backoff strategy? How do you prevent your servers from being overloaded in scenarios where you have a high number of clients that keep trying (and failing) to reestablish a connection?
You might even consider whether WebSockets is what you truly need. If your use case doesn’t require bidirectional messaging, but subscribe-only, then a protocol like SSE with stream resume baked in might be a better choice.
How We Solve It
See how Ably handles reconnections with continuity
The Ably platform enables clients to resume a stream exactly where it left off. We’ve developed this resume capability in such a way that it provides an exactly-once message delivery guarantee, rather than the inferior at-least-once or at-most-once guarantees. To achieve this, all our client SDKs are equipped to keep track of the last message that was received by a client. If a disconnection occurs, when the client is back and the connection is restored, it notifies our servers of the last message it has received.
Our resume capability allows clients that experience transient network failures to reconnect and resume a stream within two minutes. However, messages can still be retrieved even in the case of long-term disconnections (e.g. a day). For this purpose, we provide a history API.
Learn more about:
history/persisted data
handling connections with continuity
Final thoughts
Hopefully, this article has shown you what to expect when implementing a client-side WebSocket-based solution for Flutter apps. Raw WebSockets will rarely be enough, even for simple use cases. If you want to scale your system and provide optimum experiences for end-users, you will most likely have to use a feature-rich WebSocket solution.
At Ably, we’ve developed an enterprise-ready pub/sub messaging platform that makes it easy to efficiently design, quickly ship, and seamlessly scale critical realtime functionality delivered directly to end-users. We have also developed our very own WebSocket-based protocol, but we also support raw WebSockets, and other protocols, like SSE or MQTT.
Our platform is designed in a manner that enables Flutter developers to offload hard engineering and infrastructure problems to Ably, so they can focus entirely on building their products. That’s why we offer 25+ client-side SDKs, Flutter included.
If you want to talk more about WebSockets or if you’d like to find out more about Ably and how we can help you in your Flutter and WebSockets journey, get in touch or sign up for a free account.
We’ve written a lot over the years about realtime messaging and topics such as WebSockets. Here are some useful links, for further exploration:
WebSockets — A Conceptual Deep-Dive
The Ably Flutter Plugin
Ably resources & datasheets
Ably's 25+ client library SDKs
- United States Agriculture Drone market was valued at US$ 72.10 million in 2018 and is anticipated to reach US$ 144.8 million by 2025
- The use of the old fashioned paper grocery shopping bags for groceries has now been replaced by the plastic style of bags. Many restaurant owners are findin
- by Leah Williamson but was deemed illegal by German referee Marija Kurtes for an England player encroaching in the box before
- Legitimate provincial fleece carpets were, and still are, made for individual homegrown use, to be utilized as bedding just as floor covers, made by