In this article we will see how we can create a chat application in flutter using Socket IO node js server.
For this we need to have a server installed with
- Node js
- Socket IO and related dependencies
So let’ start with Flutter.
Here we need three screens
- Login screen
- Chat Users List screen
- Chat Screen.
Login Screen can be as simple as having a simple TextBox for login.
Chat Users List screen will list all the users that are available for chat.
ChatScreen will have the TextBox, send button to send messages to the selected user.
For a complete implementation, please watch the youtube tutorial below.
I am not covering the whole implementation here as it will be too big and difficult to understand.
Dependencies
We will be using the adhara_socket client which supports Socket IO client.
So add the below plugin in the pubspec.yaml file
adhara_socket_io: ^0.4.1
So let’s write a SocketUtils class in Flutter to connect to the socket.
Before that we will create a simple user class to hold the Logged in User values and also the User which the logged in user is going to chat with.
class User { int id; String name; String email; User({this.id, this.name, this.email}); factory User.fromJson(Map<String, dynamic> json) { return User( id: json["id"] as int, name: json["name"] as String, email: json["email"] as String, ); } Map<String, dynamic> toJson() => { 'id': id, 'name': name, 'email': email, }; }
Now let’s write some important parts of SocketUtils class.
static String _serverIP = YOUR_SERVER_IP; static const int SERVER_PORT = 4002; static String _connectUrl = '$_serverIP:$SERVER_PORT'; SocketIO _socket; SocketIOManager _manager; initSocket(User fromUser) async { print('Connecting user: ${fromUser.name}'); this._fromUser = fromUser; await _init(); } _init() async { _manager = SocketIOManager(); _socket = await _manager.createInstance(_socketOptions()); } _socketOptions() { final Map<String, String> userMap = { 'from': _fromUser.id.toString(), }; return SocketOptions( _connectUrl, enableLogging: true, transports: [Transports.WEB_SOCKET], query: userMap, ); } connectToSocket() { if (null == _socket) { print("Socket is Null"); return; } print("Connecting to socket..."); _socket.connect(); }
The complete source code is available in this link.
https://bitbucket.org/vipinvijayan1987/tutorialprojects/raw/f171b9775a3e381fc15be48c19a6f806ccfe0886/FlutterTutorialProjects/flutter_demos/lib/others/SocketIOChat/SocketUtils.dart
The above code will initialize a socket and connect to the Socket IO Server at port 4002;
We are saving the Logged in user and dummy users for this demo in a global Class.
Here is the complete source code of Global.dart file.
https://bitbucket.org/vipinvijayan1987/tutorialprojects/raw/f171b9775a3e381fc15be48c19a6f806ccfe0886/FlutterTutorialProjects/flutter_demos/lib/others/SocketIOChat/Global.dart
If you are running your node server in local, then change the YOUR_SERVER_IP to
static String _serverIP = Platform.isIOS ? 'http://localhost' : 'http://10.0.2.2';
So while Logging in, the logged in User will be calling initSocket from the ChatListUsersScreen.
We need to listen to connection events in the Socket Connections Events like below.
To Listen to the Socket events, add the below code in SocketUtils
setConnectListener(Function onConnect) { _socket.onConnect((data) { onConnect(data); }); } setOnConnectionErrorListener(Function onConnectError) { _socket.onConnectError((data) { onConnectError(data); }); } setOnConnectionErrorTimeOutListener(Function onConnectTimeout) { _socket.onConnectTimeout((data) { onConnectTimeout(data); }); } setOnErrorListener(Function onError) { _socket.onError((error) { onError(error); }); } setOnDisconnectListener(Function onDisconnect) { _socket.onDisconnect((data) { print("onDisconnect $data"); onDisconnect(data); }); }
You can call the above functions to register with these events from the ChatListScreen.
From the ChatListUsersScreen, we will be calling the above functions like this.
_connectSocket() { Future.delayed(Duration(seconds: 2), () async { print( "Connecting Logged In User: ${G.loggedInUser.name}, ID: ${G.loggedInUser.id}"); G.initSocket(); await G.socketUtils.initSocket(G.loggedInUser); G.socketUtils.connectToSocket(); G.socketUtils.setConnectListener(onConnect); G.socketUtils.setOnDisconnectListener(onDisconnect); G.socketUtils.setOnErrorListener(onError); G.socketUtils.setOnConnectionErrorListener(onConnectError); }); }
Here is the complete ChatListUsers Screen source code.
https://bitbucket.org/vipinvijayan1987/tutorialprojects/raw/f171b9775a3e381fc15be48c19a6f806ccfe0886/FlutterTutorialProjects/flutter_demos/lib/others/SocketIOChat/ChatUsersScreen.dart
Now we can have some custom events to send and receive messages.
The event that we are sending to send message to server is ‘single_chat_message’.
sendSingleChatMessage(ChatMessageModel chatMessageModel, User toChatUser) { print('Sending Message to: ${toChatUser.name}, ID: ${toChatUser.id}'); if (null == _socket) { print("Socket is Null, Cannot send message"); return; } _socket.emit("single_chat_message", [chatMessageModel.toJson()]); }
Call this function when you want to send a message and we will be listening to same event in the Server to resend back to the User which it is intended to.
Here is the complete source code for ChatScreen.
https://bitbucket.org/vipinvijayan1987/tutorialprojects/raw/f171b9775a3e381fc15be48c19a6f806ccfe0886/FlutterTutorialProjects/flutter_demos/lib/others/SocketIOChat/ChatScreen.dart
setOnChatMessageReceivedListener(Function onChatMessageReceived) { _socket.on(ON_MESSAGE_RECEIVED, (data) { print("Received $data"); onChatMessageReceived(data); }); }
ON_MESSAGE_RECEIVED can have the event name you set in Server.
Once the Users Logs in in the Flutter app, he will be seeing the list of users to chat in the ChatListUsersScreen.
From there he will select a user to Chat. Then open the ChatScreen and call the sendSingleChatMessage method to send message.
Later in the server side script we will be sending message back with an event name from the Socket Server.
You can register a listener like this and listen to the events from the server. Make sure you have exactly the same event name in Server and Flutter App.
Server Side
We will be using a node server as mentioned above with the Socket IO installed.
create a file named chat_server.js and lets write the methods and callbacks for accepting the incoming connection from the Flutter SocketIO Client.
Here is the partial socket script code
var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); // Reserved Events let ON_CONNECTION = 'connection'; let ON_DISCONNECT = 'disconnect'; // Main Events let EVENT_IS_USER_ONLINE = 'check_online'; let EVENT_SINGLE_CHAT_MESSAGE = 'single_chat_message'; // Sub Events let SUB_EVENT_RECEIVE_MESSAGE = 'receive_message'; let SUB_EVENT_MESSAGE_FROM_SERVER = 'message_from_server'; let SUB_EVENT_IS_USER_CONNECTED = 'is_user_connected'; let listen_port = 4002; // Status let STATUS_MESSAGE_NOT_SENT = 10001; let STATUS_MESSAGE_SENT = 10002; // This map has all users connected const userMap = new Map(); io.sockets.on(ON_CONNECTION, function (socket) { onEachUserConnection(socket); }); // This function is fired when each user connects to socket function onEachUserConnection(socket) { print('---------------------------------------'); print('Connected => Socket ID ' + socket.id + ', User: ' + JSON.stringify(socket.handshake.query)); var from_user_id = socket.handshake.query.from; // Add to Map let userMapVal = { socket_id: socket.id }; addUserToMap(from_user_id, userMapVal); print(userMap); printNumOnlineUsers(); onMessage(socket); checkOnline(socket); onUserDisconnect(socket); } function addUserToMap(key_user_id, val) { userMap.set(key_user_id, val); } function removeUserWithSocketIdFromMap(socket_id) { print('Deleting user with socket id: ' + socket_id); let toDeleteUser; for (let key of userMap) { // index 1, returns the value for each map key let userMapValue = key[1]; if (userMapValue.socket_id == socket_id) { toDeleteUser = key[0]; } } print('Deleting User: ' + toDeleteUser); if (undefined != toDeleteUser) { userMap.delete(toDeleteUser); } print(userMap); printNumOnlineUsers(); } function getSocketIDfromMapForthisUser(to_user_id) { let userMapVal = userMap.get(`${to_user_id}`); if (userMapVal == undefined) { return undefined; } return userMapVal.socket_id; } function sendBackToClient(socket, event, message) { socket.emit(event, stringifyJson(message)); } function sendToConnectedSocket(socket, to_user_socket_id, event, message) { socket.to(`${to_user_socket_id}`).emit(event, stringifyJson(message)); } function userFoundOnMap(to_user_id) { let to_user_socket_id = getSocketIDfromMapForthisUser(to_user_id); return to_user_socket_id != undefined; } // Always stringify to create proper json before sending. function stringifyJson(data) { return JSON.stringify(data); } function print(logData) { console.log(logData); } function printNumOnlineUsers() { print('Online Users: ' + userMap.size); } server.listen(listen_port);
In the above code,
io.sockets.on(ON_CONNECTION) will be fired when each user connects to socket.
Then we will add the user to a user map.
function addUserToMap(key_user_id, val) { userMap.set(key_user_id, val); }
So every user that connects with the user ID will have the map in the UserMap with socketid as value.
So the idea is get the add the socketid of every user that is connecting to server into a UserMap and then when we need to send a message from one socketid to another,
get the socketID from UserMap and call the below function.
function sendToConnectedSocket(socket, to_user_socket_id, event, message) { socket.to(`${to_user_socket_id}`).emit(event, stringifyJson(message)); }
Make sure you have the same event name in Flutterside and Server side.
The whole Server code is available in the below link
https://bitbucket.org/vipinvijayan1987/tutorialprojects/raw/f171b9775a3e381fc15be48c19a6f806ccfe0886/FlutterTutorialProjects/flutter_demos/socket_io_server_side_script/socket_server.js
Complete Source Code is available in the below Git Repository.
https://bitbucket.org/vipinvijayan1987/tutorialprojects/src/SocketIOChatDemo/FlutterTutorialProjects/flutter_demos/
Make sure to use the branch SocketIOChatDemo for the Socket IO Chat Code.
Watch the youtube tutorial for better and complete understanding.
Does this example also save the messages for users who are not online on that moment?
Hi Ro, This is not saving the messages, you can use your database for that.
Hi James, thanks, do you also have an tutorial for this?
Not yet.
Can I contact you for hiring you for this?
sure
Pingback: #Google's Flutter Tutorial - Complete Chat Application in Flutter using Socket IO (coderzheaven.com) - TutsFx