Introduction
WebSockets are very beautiful tools that allows us to establish a realtime communication in modern web applications. In fact this mechanism is so powerfull and it's used to build different kind of apps like realtime chat or notification system etc ..
In this article we will show you how to build a realtime chat app using android nodeJs and Socket.io
Getting started
Our chat app is divded into 2 part :
1- Server side : a node js server with the implementation of socket.io for server
2- Client side : creating the android app and implementing socket.io for client
Our NodeJs Server
well , to make things clear our project architecture will be composed of 2 files :
package.json which will handle all the depandencies for our node js app and index.js which will be our main server .
After creating the two files , we open the commande line under our project
directory and execute this command
npm install --save express socket.io
now in our index.js file we will build our server and make all the configurations so that it will look like this
const express = require('express'),
http = require('http'),
app = express(),
server = http.createServer(app),
io = require('socket.io').listen(server);
app.get('/', (req, res) => {
res.send('Chat Server is running on port 3000')
});
server.listen(3000,()=>{
console.log('Node app is running on port 3000')
});
to make sure that our server is runing go to the command line under our project directory and execute this command
node index.js
NOTE: using node command we can run any server created with node environment but the problem is that we have to run the same commande every time we update our index.js file , so to make things more simple we can use nodemon command which will automatically restart our server every time we make changes
so to install nodemon go to your command line and run
npm install -g nodemon
to make sure that our project is running we should see this log in our console
now comes the best part !!
we will try now to implement some socket.io methods in our server to handle all the events of our chat app including users connection states and messages .
in our index.js file we add the first implementation that will detect if we have a user connected to our server
io.on('connection', (socket) => {
console.log('user connected')
socket.on('join', function(userNickname) {
console.log(userNickname +" : has joined the chat " )
socket.broadcast.emit('userjoinedthechat',userNickname +" : has joined the chat ")
});
});
Actually socket.io mechanism is based on listening and firing events , in this first implementation that we have done the (on) method which takes two parameters ('eventname',callback) defines a listener to an event called connection and this event will be fired from the client side so that node js can handle it , after that we defined a method that will listen to an emitted event called 'join' and will log the name of the user who has join the chat in the console .
Now when node js detect a user it fires an event to the client side called 'userjoinedthechat' using the method emit , note that socket.broadcast.emit will send the event to every single user connected to the server except the sender .
if we want to send the message to all users including the sender we just have to use io.emit() instead of socket.emit().
Now to handle messages we add these few lines and we can see that we have added extra argument to the callback function which are the user nickname and the message content , actually these informations will be sent from the client side when firing the event 'messagedetection'
socket.on('messagedetection', (senderNickname,messageContent) => {
//log the message in console
console.log(senderNickname+" :" +messageContent)
//create a message object
let message = {"message":messageContent, "senderNickname":senderNickname}
// send the message to the client side
socket.emit('message', message )
});
And finally when the user disconnect from the client side , the event will be handled by this implementation
socket.on('disconnect', function() {
console.log( 'user has left ')
socket.broadcast.emit( "userdisconnect" ,' user has left')
});
Now that our server is ready the index.js file should look like this
const express = require('express'),
http = require('http'),
app = express(),
server = http.createServer(app),
io = require('socket.io').listen(server);
app.get('/', (req, res) => {
res.send('Chat Server is running on port 3000')
});
io.on('connection', (socket) => {
console.log('user connected')
socket.on('join', function(userNickname) {
console.log(userNickname +" : has joined the chat " );
socket.broadcast.emit('userjoinedthechat',userNickname +" : has joined the chat ");
})
socket.on('messagedetection', (senderNickname,messageContent) => {
//log the message in console
console.log(senderNickname+" : " +messageContent)
//create a message object
let message = {"message":messageContent, "senderNickname":senderNickname}
// send the message to all users including the sender using io.emit()
io.emit('message', message )
})
socket.on('disconnect', function() {
console.log(userNickname +' has left ')
socket.broadcast.emit( "userdisconnect" ,' user has left')
})
})
server.listen(3000,()=>{
console.log('Node app is running on port 3000')
})
Our Android app (Socket client)
To start open android studio and create a new projet with an empty activity , after that open app build.gradle file and add these dependencies then synchronize your project.
compile 'com.android.support:recyclerview-v7:25.3.1'
compile('com.github.nkzawa:socket.io-client:0.5.0') {
exclude group: 'org.json', module: 'json'
}
Now about these lines :
the first one is the recycler view that we will use to display the list of our messages and the second one is the library that will provid us with the implementation of socket.io for the client side so that we can fire or listen to events .
don't forget to enable INTERNET permission in you manifest.xml
<uses-permission android:name="android.permission.INTERNET" ></uses-permission>
In activity_main.xml we will add an EditText for the user to put his nickname and a button that allows him to enter the chatbox
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.aymen.androidchat.MainActivity">
<EditText
android:id="@+id/nickname"android:layout_centerInParent="true"android:textSize="30dp"android:hint="Enter your nickname !"android:layout_width="match_parent"android:layout_height="wrap_content" /><Buttonandroid:layout_below="@+id/nickname"android:id="@+id/enterchat"android:text="Go to chat "android:layout_width="match_parent"android:layout_height="wrap_content" />
</RelativeLayout>
so that the preview will look like this
now your MainActivity.java should look like this
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
private Button btn;
private EditText nickname;
public static final String NICKNAME = "usernickname";
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//call UI components by id
btn = (Button)findViewById(R.id.enterchat) ;
nickname = (EditText) findViewById(R.id.nickname);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//if the nickname is not empty go to chatbox activity and add the nickname to the intent extra
if(!nickname.getText().toString().isEmpty()){
Intent i = new Intent(MainActivity.this,ChatBoxActivity.class);
//retreive nickname from EditText and add it to intent extra
i.putExtra(NICKNAME,nickname.getText().toString());
startActivity(i);
}
}
});
}
}
Now create a second empty activity called ChatBoxActivity and in activity_chat_box.xml add these lines
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.aymen.androidchat.ChatBoxActivity">
<LinearLayout
android:weightSum="3"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerViewandroid:layout_weight="3"android:id="@+id/messagelist"android:layout_width="match_parent"android:layout_height="wrap_content"android:clipToPadding="false"android:scrollbars="vertical"/><Viewandroid:layout_marginTop="5mm"android:id="@+id/separator"android:layout_width="match_parent"android:layout_height="1dp"android:background="@android:color/darker_gray"/>
<LinearLayoutandroid:weightSum="3"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content">
<EditText
android:id="@+id/message"android:layout_weight="3"android:layout_width="wrap_content"android:hint="your message"
android:layout_height="match_parent" />
<Button
android:id="@+id/send"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#00000000"android:text="send"
/></LinearLayout>
</LinearLayout>
</RelativeLayout>
your preview should look like this
Now before implementing the socket client we should create an adapter to handle and display our messages for that we need to create a file called item.xml and a java class called message which have two simple string properties (nickname,message) .
In our project directory along side with activites create a file called Message.java :
public class Message {
private String nickname;
private String message ;
public Message(){
}
public Message(String nickname, String message) {
this.nickname = nickname;
this.message = message;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
now create a file called item.xml under layout directory and add these lines
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal" android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@id/nickname"android:textSize="15dp"android:textStyle="bold"android:text="Nickname : "android:layout_width="wrap_content"android:layout_height="wrap_content" /><TextViewandroid:id="@id/message"android:textSize="15dp"android:text=" message "android:layout_width="wrap_content"android:layout_height="wrap_content" />
</LinearLayout>
create a file called ChatBoxAdapter.java and put these lines
package com.example.aymen.androidchat;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
public class ChatBoxAdapter extends RecyclerView.Adapter<ChatBoxAdapter.MyViewHolder> {
private List<Message> MessageList;
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView nickname;
public TextView message;
public MyViewHolder(View view) {
super(view);
nickname = (TextView) view.findViewById(R.id.nickname);
message = (TextView) view.findViewById(R.id.message);
}
}
// in this adaper constructor we add the list of messages as a parameter so that
// we will passe it when making an instance of the adapter object in our activity
public ChatBoxAdapter(List<Message>MessagesList) {
this.MessageList = MessagesList;
}
@Overridepublic int getItemCount() {
return MessageList.size();
}
@Overridepublic ChatBoxAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item, parent, false);
return new ChatBoxAdapter.MyViewHolder(itemView);
}
@Overridepublic void onBindViewHolder(final ChatBoxAdapter.MyViewHolder holder, final int position) {
//binding the data from our ArrayList of object to the item.xml using the viewholder
Message m = MessageList.get(position);
holder.nickname.setText(m.getNickname());
holder.message.setText(m.getMessage() );
}
}
now with everything setup we can implement the socket client in our ChatBoxActivity.java so this is how we are going to proceed :
1.Get the nickname of the user from the intent extra
2.call and implement all the methods relative to recycler view including the adapter instanciation
3.declare and define the host for socket client to make connection with the server
4.handle all events fired from the server
5.emit events when user connect , disconnect or send a message
but before that let's check if everything is ok or not so in our ChatBoxActivity we will declare the socket object and add the socket connection in the method onCreate so that when the activity is called the socket client will directly fire the event connection
public class ChatBoxActivity extends AppCompatActivity {
//declare socket object
private Socket socket;
private String Nickname ;
@Overrideprotected
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat_box);
// get the nickame of the user
Nickname= (String)getIntent().getExtras().getString(MainActivity.NICKNAME);
//connect you socket client to the server
try {
//if you are using a phone device you should connect to same local network as your laptop and disable your pubic firewall as well
socket = IO.socket("http://yourlocalIPaddress:3000");
//create connection
socket.connect()
// emit the event join along side with the nickname
socket.emit('join',Nickname);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
now run your emulator and enter a nickname in the first activity then click go to chat you will see a log in your server console that indicates that a user has successfully made a connection with the server and we can see that the listener for the fired event join in our server is working properly so that it logs the name of the user connected
now with everything working , we should not forget that when our server handle an event it broadcast other costum events as well and so those fired events should be handled in the client side , for that we will make the first listener for the event "userjoinedthechat" which is a custom event fired when the server handle the event "join".
in our ChatBoxActivity we will add these lines
socket.on("userjoinedthechat", new Emitter.Listener() {
@Overridepublic void call(final Object... args) {
runOnUiThread(new Runnable() {
@Overridepublic void run() {
String data = (String) args[0];
// get the extra data from the fired event and display a toast
Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();
}
});
}
now we run 2 emulator in the same time and we enter two different nicknames from both sides and as we can see that one of the two emulator indicated that a user has successfully joined the chat
now comes the best part of our app which is the chat messages :
to display the messages we have to proceed this way
1.add onclickListener to the button send and grab the message content from the EditText after that emit the event "messagedetection" using emit() method along side with the nickname of the sender and the message content
2.the event will be handled by the server and broadcasted to all users
3.adding a socket listener in android to listen for the event "message" fired by the server
4.extract the nickname and the message from the extra data and make a new instance of the object Message
5.adding the instance to the ArrayList of messages and notify the adapter to update the recycler view
But before that let's setup our recyler view , Adapter , message textfield and the button send.
Add the declarations below in ChatBoxActivity
public RecyclerView myRecylerView ;
public List<Message> MessageList ;
public ChatBoxAdapter chatBoxAdapter;
public EditText messagetxt ;
public Button send ;
in the method onCreate add these lines
messagetxt = (EditText) findViewById(R.id.message) ;
send = (Button)findViewById(R.id.send);
MessageList = new ArrayList<>();
myRecylerView = (RecyclerView) findViewById(R.id.messagelist);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
myRecylerView.setLayoutManager(mLayoutManager);
myRecylerView.setItemAnimator(new DefaultItemAnimator());
Now in your ChatBoxActivity the button action should look like this
send.setOnClickListener(new View.OnClickListener() {
@Overridepublic void onClick(View v) {
//retrieve the nickname and the message content and fire the event messagedetection
if(!messagetxt.getText().toString().isEmpty()){
socket.emit("messagedetection",Nickname,messagetxt.getText().toString());
messagetxt.setText(" ");
}
}
});
and the listener should look like this
socket.on("message", new Emitter.Listener() {
@Overridepublic void call(final Object... args) {
runOnUiThread(new Runnable() {
@Overridepublic void run() {
JSONObject data = (JSONObject) args[0];
try {
//extract data from fired event
String nickname = data.getString("senderNickname");
String message = data.getString("message");
// make instance of message
Message m = new Message(nickname,message);
//add the message to the messageList
MessageList.add(m);
// add the new updated list to the adapter
chatBoxAdapter = new ChatBoxAdapter(MessageList);
// notify the adapter to update the recycler view
chatBoxAdapter.notifyDataSetChanged();
//set the adapter for the recycler view
myRecylerView.setAdapter(chatBoxAdapter);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
});
as we can see in the screenshot below everything is working properly :)) and the messages are displaying from both sides , note that we can connect with many other users but we just have to run other emulators and enter nicknames to join the chatbox
before ending this tutorial we have to make our last functionnality which detects if the user has disconnected from the chatbox .
In our ChatBoxActivity override the method onDestroy() and add these lines
@Override
protected void onDestroy() {
super.onDestroy();
socket.disconnect();
}
and for the listener
socket.on("userdisconnect", new Emitter.Listener() {
@Overridepublic void call(final Object... args) {
runOnUiThread(new Runnable() {
@Overridepublic void run() {
String data = (String) args[0];
Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();
}
});
}
});
finally our ChatBoxActivity will look like this
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.github.nkzawa.emitter.Emitter;
import com.github.nkzawa.socketio.client.IO;
import com.github.nkzawa.socketio.client.Socket;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
public class ChatBoxActivity extends AppCompatActivity {
public RecyclerView myRecylerView ;
public List<Message> MessageList ;
public ChatBoxAdapter chatBoxAdapter;
public EditText messagetxt ;
public Button send ;
//declare socket objectprivate Socket socket;
public String Nickname ;
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat_box);
messagetxt = (EditText) findViewById(R.id.message) ;
send = (Button)findViewById(R.id.send);
// get the nickame of the user
Nickname= (String)getIntent().getExtras().getString(MainActivity.NICKNAME);
//connect you socket client to the servertry {
socket = IO.socket("http://yourlocalIPaddress:3000");
socket.connect();
socket.emit("join", Nickname);
} catch (URISyntaxException e) {
e.printStackTrace();
}
//setting up recyler
MessageList = new ArrayList<>();
myRecylerView = (RecyclerView) findViewById(R.id.messagelist);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
myRecylerView.setLayoutManager(mLayoutManager);
myRecylerView.setItemAnimator(new DefaultItemAnimator());
// message send action
send.setOnClickListener(new View.OnClickListener() {
@Overridepublic void onClick(View v) {
//retrieve the nickname and the message content and fire the event messagedetectionif(!messagetxt.getText().toString().isEmpty()){
socket.emit("messagedetection",Nickname,messagetxt.getText().toString());
messagetxt.setText(" ");
}
}
});
//implementing socket listeners
socket.on("userjoinedthechat", new Emitter.Listener() {
@Overridepublic void call(final Object... args) {
runOnUiThread(new Runnable() {
@Overridepublic void run() {
String data = (String) args[0];
Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();
}
});
}
});
socket.on("userdisconnect", new Emitter.Listener() {
@Overridepublic void call(final Object... args) {
runOnUiThread(new Runnable() {
@Overridepublic void run() {
String data = (String) args[0];
Toast.makeText(ChatBoxActivity.this,data,Toast.LENGTH_SHORT).show();
}
});
}
});
socket.on("message", new Emitter.Listener() {
@Overridepublic void call(final Object... args) {
runOnUiThread(new Runnable() {
@Overridepublic void run() {
JSONObject data = (JSONObject) args[0];
try {
//extract data from fired event
String nickname = data.getString("senderNickname");
String message = data.getString("message");
// make instance of message
Message m = new Message(nickname,message);
//add the message to the messageList
MessageList.add(m);
// add the new updated list to the dapter
chatBoxAdapter = new ChatBoxAdapter(MessageList);
// notify the adapter to update the recycler view
chatBoxAdapter.notifyDataSetChanged();
//set the adapter for the recycler view
myRecylerView.setAdapter(chatBoxAdapter);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
socket.disconnect();
}
}
Conclusion
In this exemple we had a great look at the usage of socket.io along side with node js and android , we tried as well to explain some basics and understand the mechanism of socket.io and how to establish a bi-directional communications between a client and a server , note that there are other tools in socket.io like rooms and namespaces which could be very helpfull to make beautifull web and mobile apps .
find in these related links the two projects :
client side :https://github.com/medaymenTN/AndroidChat
server side: https://github.com/medaymenTN/NodeJSChatServer
Top comments (17)
What about when i send message and other device is offline.
You don't add this functionality.
Node.js is not reliable solution for chat applications. There is better options out there. Like XMPP server which is most trusted and reliable solutions.
i didn't say that node js can do everything but at least the socket.io implementation is very simple and there are a lot of features that you can add.
i'v tried to establish a socket server once in java and it was like hell but with node js i'v totally love doing it coz it was just less than 20 line of code and everything works like a charm
XMPP servers like OPENFIRE also totally built in java and provides much more functionality than node.js sockets.
You can store messages in a database like redis and mark them unread and then serve them when the user logs back in. This is the approach I am using right now, let me know if something is wrong with this approach.
hey brother can you please help to make me understadable how do you work this such thing
im a student of 2nd year and i want to gain knowledeg
How will the socket.on listener work if the chat activity is not open ? I want the listener to run in the background all the time so whenever the new message is sent , it updates the Ui even if i am not using the app
Hi
I wrote exactly the same code as you, but the ("a user connected") sentence is not displayed in cmd.
I have been involved in this issue for two months and there is nothing left on the stackoverflow and GitHub that I have not read.
I checked all the side works such as targetSandBoxVersion , clearTextTrafic , network_security_config , test https and http , open ports with telnet , test 10.0.2.2 and 127.0.0.1 urls , and etc .
Also My laptop does not have any server connection problem because I can send and receive http requests.
Could it be a problem with the android websocket firewall?
I'm really tired.
Please help me if anyone has a similar problem or knows the solution.
Hey, I modified this a bit to create a Calibration App, so the App sends its coordinates to the Server whenever you touch the screen. It worked briefly, but it doesn't work anymore.
If someone could take I quick look over my code and tell me whats wrong with it, it would be greatly appreciated.
Heres a link to a Stackoverflow Post I created: stackoverflow.com/q/60074935/12845039
There you will find the code.
welcome,
How to modify these applications and add to them the features of the paid version? For example the application found here لايت موشن مهكر
Intriguing data yet it will be great to see on training. Will attempt to play out this code today. Likewise, it will be nice to make a cheating application to be utilized inside this visit. I talk about something like Snapchat spy applications portrayed here bestcellphonespyapps.com/best-cell... that are utilized to follow the movement of another client in Snapchat and different couriers.
Interesting information but it will be good to see on practice. Will try to perform this code today. Also, it will be good to make a cheating app to be used within this chat. I speak about something like Snapchat spy apps described here celltrackingapps.com/snapchat-chea... that are used to track the activity of another user in Snapchat and other messengers.
How will the socket.on listener work if the chat activity is not open ? I want the listener to run in the background all the time so whenever the new message is sent , it updates the Ui even if i am not using the app
What is the difference between the library you use
'com.github.nkzawa:socket.io-client:0.5.0'
and between the official one from socketIO
io.socket:socket.io-client:1.0.0
Sir, How can I use my domain/hosting instead of localhost?
Thank you.
THANKS