loading...
Cover image for Creating a realtime chat app with android , NodeJs and Socket.io

Creating a realtime chat app with android , NodeJs and Socket.io

medaymentn profile image medaymenTN ・13 min read

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

Alt text of image

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
Alt text of image

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
Alt text of image

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

Alt text of image

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

Alt text of image

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

Alt text of image

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

Posted on Jul 17 '18 by:

Discussion

markdown guide
 

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, 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.

 

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.

 

Your project was a total lifesaver during a hackathon! Hats off!