Chat Application in Flutter Using Socket IO

By | May 18, 2020

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.

Leave a Reply

Your email address will not be published. Required fields are marked *