WebSockets in Flutter
Apart from establishing connections with servers using normal HTTP requests, you can also use WebSockets. WebSockets make it possible to have bidirectional (two-way) communication with a server without polling. Therefore, leveraging on WebSockets in Flutter will allow you to create interactive and responsive applications which can handle real-time data updates.
WebSockets vs HTTP
As mentioned above, both of these techniques can be used to communicate with a server. But even though both aim to deliver up-to-date information, they differ significantly in the following ways.
Connection model:
WebSockets establish a persistent and continuous bidirectional connection between the client and the server. Thus, there is no overhead associated with repeatedly establishing new connections every time.
HTTP polling relies on the request and response model. That is, the client sends requests periodically to the server in order to check and fetch updates.
Latency:
Because WebSockets maintain a persistent connection between the client and the server, there is minimal latency experienced. The constant HTTP request-response polling introduces latency.
Data usage:
WebSockets consume less resources and are more efficient in handling large volumes of data. With HTTP, data usage is higher because the frequent requests place a significant load on the server.
Complexity:
HTTP is simpler to implement at a basic level as compared to WebSockets which require server-side support.
WebSockets revolutionised the web, turning clunky, slow real-time interactions into sleek, low-latency experiences, making them the go-to for dynamic, user-friendly applications.
Implementation
For this demonstration, we’re going to be using an echo WebSocket. This is a server which simply returns (or echoes) and message it receives. The client (us) will send a message to the server and the server will immediately return the same message. So it is primarily just used for testing and debugging. E.g., wss://ws.postman-echo.com/raw
or ws://echo.websocket.org
Let’s get started with WebSocket implementation in Flutter. Add the latest web_socket_channel
package to your dependencies and remember to run flutter pub get
to install it.
dependencies:
flutter:
sdk: flutter
web_socket_channel: ^3.0.2
In your dart file, add the package import at the top: import ‘package:web_socket_channel/io.dart’;
and proceed to initialise the WebSocket channel, together with other variables — the TextEditingController
to handle the client input in the TextField and the List
to store the sent and received messages .
final channel = IOWebSocketChannel.connect('wss://ws.postman-echo.com/raw');
final TextEditingController _controller = TextEditingController();
final List<String> _messages = [];
In the initState()
function which is run when the widget is first created we’ll be listening in to incoming WebSocket messages and update the _messages
List using setState()
so that the UI reflects the changes. You can also handle the channel errors here.
@override
void initState() {
super.initState();
channel.stream.listen(
(message) {
setState(() {
_messages.add('[recv]: $message');
});
},
onError: (error) {
print('WebSocket Error: $error');
},
onDone: () {
print('WebSocket Closed');
},
);
}
The UI will mainly comprise of two parts: ListView
to display the sent and received messages,Row
with TextFormField
for the user to enter the actual messages to be echoed to the server and an IconButton
to trigger the _sendMessage
function.
...
Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _messages.length,
itemBuilder: (context, index) {
return ListTile(title: Text(_messages[index])); // displays the send and received messages
},
),
),
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black12),
borderRadius: BorderRadius.only(
topRight: Radius.circular(10),
topLeft: Radius.circular(10),
),
),
child: Row(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.fromLTRB(10, 0, 0, 10),
child: TextFormField(
controller: _controller,
maxLines: 1,
style: TextStyle(fontSize: 14),
decoration: InputDecoration(
hintText: 'Send a message to echo...',
border: InputBorder.none,
),
),
),
),
IconButton(
onPressed: _sendMessage, // function to send message to the server
icon: Icon(Icons.send),
),
],
),
),
],
),
...
The _sendMessage
function starts by checking that the input field is not empty. If it isn’t we store the message in the List so that it appears in the chat and then we send the message to the server via channel.sink.add()
. Finally, we just clear the input field.
void _sendMessage() {
if (_controller.text.isNotEmpty) {
String text = _controller.text;
setState(() {
_messages.add('[send]: $text');
});
channel.sink.add(text);
_controller.clear();
}
}
As always, remember to close the channels and dispose any controllers to avoid memory leaks.
@override
void dispose() {
channel.sink.close();
_controller.dispose();
super.dispose();
}
And this is how it will work.
The full code for this implementation can be found on my repo here. So check it out and share your thoughts.
“When you can connect with a live audience and you get that immediate response it’s just great.”
— Limahl
Thank you for reading ❤