Configuring WebSocket in Flutter and Spring-Boot

Lejdi Prifti
5 min readOct 16, 2023

--

In this article, we are going to look at creating a simple chat application using Spring-Boot and Flutter. This will help us understand how to configure WebSocket in Flutter and Spring-Boot with SockJS.

What is WebSocket?

WebSocket is a communication protocol that enables real-time, bidirectional data transfer between a client (usually a web browser) and a server over a single, long-lived connection. Unlike traditional HTTP requests, which are stateless and involve separate requests and responses, WebSocket allows for continuous data exchange without the overhead of repeatedly establishing new connections.

We will use the server we built in the last post as a server for our application.

Let’s keep our focus on building a great mobile application with Flutter.

For this application, we will need a very important library that is stomp_dart_client .

Your dependencies section in the file pubspec.yaml must look like this.

# ...
dependencies:
flutter:
sdk: flutter
stomp_dart_client: 1.0.0

# ...

The chart.dart file, which will be created next, will most notably contain the WebSocket configurations required to connect to the SpringBoot server we installed in the previous article as well as the chat view, which includes an input, a floating action to send a message, and the actual messages themselves.

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:stomp_dart_client/stomp.dart';
import 'package:stomp_dart_client/stomp_config.dart';
import 'package:stomp_dart_client/stomp_frame.dart';

class Chat extends StatefulWidget {
final String chatId;
final String userId;
const Chat({super.key, required this.chatId, required this.userId});

@override
ChatState createState() => ChatState();
}

class ChatState extends State<Chat> {
final String webSocketUrl = 'ws://192.168.1.7:8080/websocket';
late StompClient _client;
final TextEditingController _controller = TextEditingController();
List<dynamic> messages = List.empty();
@override
void initState() {
super.initState();
_client = StompClient(
config: StompConfig(url: webSocketUrl, onConnect: onConnectCallback));
_client.activate();
}

void onConnectCallback(StompFrame connectFrame) {
_client.subscribe(
destination: '/topic/chat/${widget.chatId}',
headers: {},
callback: (frame) {
print(frame.body);
// Received a frame for this subscription
messages = jsonDecode(frame.body!).reversed.toList();
});
}

void _sendMessage() {
final message = _controller.text;
if (message.isNotEmpty) {
_client.send(
destination: '/app/chat/${widget.chatId}', // Replace with your chat ID
body: json.encode({
'data': message,
'userId': widget.userId
}), // Format the message as needed
);
_controller.clear();
}
}

@override
Widget build(BuildContext context) {
double screenHeight = MediaQuery.of(context).size.height - 250;
return Scaffold(
appBar: AppBar(
title: Text('Chat'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Form(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(labelText: 'Send a message'),
),
),
const SizedBox(height: 24),
SingleChildScrollView(
child: Container(
height: screenHeight, // Set a fixed height here
child: ListView.builder(
itemCount: messages.length,
itemBuilder: (context, index) {
// Extract the item from the list
Map<String, dynamic> item = messages[index];

// Create a Text widget for the item
return ListTile(
title: Text(item['data']),
// You can add more widgets here, e.g., icons, buttons, etc.
);
},
),
),
)
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _sendMessage,
tooltip: 'Send message',
child: const Icon(Icons.send),
),
);
}

@override
void dispose() {
_client.deactivate();
_controller.dispose();
super.dispose();
}
}

Let’s break it down.

The code begins with import statements, including the dart:convert library for JSON encoding and decoding, and several packages related to Flutter and the Stomp WebSocket client.

Chat is a stateful widget that represents the chat interface. It takes two required parameters: chatId and userId. The widget defines a WebSocket URL, initializes a Stomp client, and maintains a list of messages.

The initState method is called when the widget is created. In this method, the Stomp client is configured with the WebSocket URL and a callback function to be executed when a connection is established. The client is then activated.

The onConnectCallbackmethod is called when the Stomp client successfully connects to the WebSocket server. It subscribes to a destination topic for receiving messages and defines a callback function to process incoming messages. In this callback, the received JSON message is decoded, reversed, and stored in the messages list. The reason it is reversed is due to the way the messages are stored in the server developed in this article.

The _sendMessagemethod is called when the user sends a message. It sends the message to a specific destination topic on the server, along with the user’s ID. The message is JSON-encoded.

The build method defines the user interface of the chat screen. It consists of an app bar, a text input field for sending messages, a scrollable list of messages, and a floating action button for sending messages.

Inside the ListView.builder, the messages list is iterated through to display each message. The list is reversed, so newer messages appear at the top. Each message is displayed in a ListTile widget, containing a Text widget to display the message content.

The dispose method is used to clean up resources when the widget is removed. It deactivates the Stomp client, disposes of the text controller, and calls the parent's dispose method.

main.dart

On main.dart , we would have the following implementation to display a Chat interface in a hard-coded way.

import 'package:flutter/material.dart';
import 'package:flutter_chatapp/chat.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Chat Application',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const Chat(chatId: '1', userId: 'lejdi'));
}
}

If you are interested, keep an eye on this repository to see how this application will develop.

--

--

Lejdi Prifti

Software Developer | ML Enthusiast | AWS Practitioner | Kubernetes Administrator