Monday, August 18, 2014

Implement simple Android Chat Application, server side.

It's a simple example of Chat app running on Android devices, both server and clients. One device run AndroidChatServer to wait connection from clients, other devices run AndroidChatClient to connect to the server. Any AndroidChatClient can send message to server, then server broadcast to all clients.


Here is the code in Server side, Client side can be found in next post, "Simple Android Chat Application, client side"
.
MainActivity.java
package com.example.androidchatserver;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import android.support.v7.app.ActionBarActivity;
import android.widget.TextView;
import android.widget.Toast;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity {

 static final int SocketServerPORT = 8080;

 TextView infoIp, infoPort, chatMsg;

 String msgLog = "";
 
 List<ChatClient> userList;

 ServerSocket serverSocket;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  infoIp = (TextView) findViewById(R.id.infoip);
  infoPort = (TextView) findViewById(R.id.infoport);
  chatMsg = (TextView) findViewById(R.id.chatmsg);

  infoIp.setText(getIpAddress());

  userList = new ArrayList<ChatClient>();
  
  ChatServerThread chatServerThread = new ChatServerThread();
  chatServerThread.start();
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();

  if (serverSocket != null) {
   try {
    serverSocket.close();
   } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
 }

 private class ChatServerThread extends Thread {

  @Override
  public void run() {
   Socket socket = null;

   try {
    serverSocket = new ServerSocket(SocketServerPORT);
    MainActivity.this.runOnUiThread(new Runnable() {

     @Override
     public void run() {
      infoPort.setText("I'm waiting here: "
        + serverSocket.getLocalPort());
     }
    });
    
    while (true) {
     socket = serverSocket.accept();
     ChatClient client = new ChatClient();
     userList.add(client);
     ConnectThread connectThread = new ConnectThread(client, socket);
     connectThread.start();
    }

   } catch (IOException e) {
    e.printStackTrace();
   } finally {
    if (socket != null) {
     try {
      socket.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }
   }

  }

 }
 
 private class ConnectThread extends Thread {
  
  Socket socket;
  ChatClient connectClient;
  String msgToSend = "";

  ConnectThread(ChatClient client, Socket socket){
   connectClient = client;
   this.socket= socket;
   client.socket = socket;
   client.chatThread = this;
  }

  @Override
  public void run() {
   DataInputStream dataInputStream = null;
   DataOutputStream dataOutputStream = null;
   
   try {
    dataInputStream = new DataInputStream(socket.getInputStream());
    dataOutputStream = new DataOutputStream(socket.getOutputStream());
    
    String n = dataInputStream.readUTF();
    
    connectClient.name = n;
    
    msgLog += connectClient.name + " connected@" + 
      connectClient.socket.getInetAddress() + 
      ":" + connectClient.socket.getPort() + "\n";
    MainActivity.this.runOnUiThread(new Runnable() {

     @Override
     public void run() {
      chatMsg.setText(msgLog);
     }
    });
    
    dataOutputStream.writeUTF("Welcome " + n + "\n");
    dataOutputStream.flush();
    
    broadcastMsg(n + " join our chat.\n");
    
    while (true) {
     if (dataInputStream.available() > 0) {
      String newMsg = dataInputStream.readUTF();
      
      
      msgLog += n + ": " + newMsg;
      MainActivity.this.runOnUiThread(new Runnable() {

       @Override
       public void run() {
        chatMsg.setText(msgLog);
       }
      });
      
      broadcastMsg(n + ": " + newMsg);
     }
     
     if(!msgToSend.equals("")){
      dataOutputStream.writeUTF(msgToSend);
      dataOutputStream.flush();
      msgToSend = "";
     }
     
    }
    
   } catch (IOException e) {
    e.printStackTrace();
   } finally {
    if (dataInputStream != null) {
     try {
      dataInputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }

    if (dataOutputStream != null) {
     try {
      dataOutputStream.close();
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
    }
    
    userList.remove(connectClient);
    MainActivity.this.runOnUiThread(new Runnable() {

     @Override
     public void run() {
      Toast.makeText(MainActivity.this, 
       connectClient.name + " removed.", Toast.LENGTH_LONG).show();
      
      msgLog += "-- " + connectClient.name + " leaved\n";
      MainActivity.this.runOnUiThread(new Runnable() {

       @Override
       public void run() {
        chatMsg.setText(msgLog);
       }
      });
      
      broadcastMsg("-- " + connectClient.name + " leaved\n");
     }
    });
   }
   
  }
  
  private void sendMsg(String msg){
   msgToSend = msg;
  }
  
 }
 
 private void broadcastMsg(String msg){
  for(int i=0; i<userList.size(); i++){
   userList.get(i).chatThread.sendMsg(msg);
   msgLog += "- send to " + userList.get(i).name + "\n";
  }
  
  MainActivity.this.runOnUiThread(new Runnable() {

   @Override
   public void run() {
    chatMsg.setText(msgLog);
   }
  });
 }

 private String getIpAddress() {
  String ip = "";
  try {
   Enumeration<NetworkInterface> enumNetworkInterfaces = NetworkInterface
     .getNetworkInterfaces();
   while (enumNetworkInterfaces.hasMoreElements()) {
    NetworkInterface networkInterface = enumNetworkInterfaces
      .nextElement();
    Enumeration<InetAddress> enumInetAddress = networkInterface
      .getInetAddresses();
    while (enumInetAddress.hasMoreElements()) {
     InetAddress inetAddress = enumInetAddress.nextElement();

     if (inetAddress.isSiteLocalAddress()) {
      ip += "SiteLocalAddress: "
        + inetAddress.getHostAddress() + "\n";
     }

    }

   }

  } catch (SocketException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   ip += "Something Wrong! " + e.toString() + "\n";
  }

  return ip;
 }

 class ChatClient {
  String name;
  Socket socket;
  ConnectThread chatThread;
  
 }

}

/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Char Server"
        android:textStyle="bold" />
    
    <TextView
        android:id="@+id/infoport"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="italic" />

    <TextView
        android:id="@+id/infoip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="italic" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <TextView
            android:id="@+id/chatmsg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </ScrollView>

</LinearLayout>

make sure Add uses-permission of "android.permission.INTERNET" to AndroidManifest.xml.

download filesDownload the files.

Related:
- Command line version Chat Server.

Next:
Android Chat example, with server sending individual message to specify client.


Added@2014-12-06

If you found "Something Wrong! java.net.SocketException" displayed.

Make sure add permission of "android.permission.INTERNET" in AndroidManifest.xml. It's a common error of missing "android.permission.INTERNET" to cause SocketException.


27 comments:

Unknown said...

Simply awesome!!! :) Just awesome... Thank you so much for this mind blowing tutorial...!

senurz said...

when I try to run this an error occurs saying "ActionBarActivity cannot be resolved to a type"
any idea why that is?

Erik said...

hello senurz,

Is it your case? "The import android.support.v7 cannot be resolved" and "ActionBarActivity cannot be resolved to a type"

senurz said...

Adding 'android-support-v7-appcompat' support library to the project did the trick!
Thank you Andr.oid Eric!

Erik said...

Hello Kissy Mae Padayjag,

Are you miss "android.permission.INTERNET" in AndroidManifest.xml?

Please check the post updated with video.

eisha said...

Nice tutorial!!
I am facing error in styles.xml(in res
->values folder).. just downloaded the source code from your site

kindly reply urgent!!

Unknown said...

It's like getting the water in the sand. Feeling great after 2 days of R & D on Socket Programming i got the right working solution

Anonymous said...

Thanks for the example. I found out that readUTF uses a modified UTF-16 encoding. The size of buffer your sending needs be the first byte of what your sending. Something like '123' would actually translate to { 0, 3, 0, 49, 0, 50, 0, 51 } in raw bytes ... I hope that saves someone a headache when writing a client in a different language.

shubham said...

sir i cannot download the attachments so i copy pasted the code and updated the manifest and copy pasted a android-support-v7-appcompat.jar to project but it stops on start saying NoClassDefFound

Farrukh Javed said...

Hi.i am developing an app in which i used your code for Server/Client connections...my app is getting crashed when i try to run it...in log cat the error shown was related to when i try to add Chat-Client to
userList.when i use this List in my code it throws null pointer exception...please help me solve this issue

salma said...

i use eclipse target 17 and i run this application on 2 emulator android on eclipse but i have an exception connection refused,plz help me

Unknown said...

every time i click connect i get

java.net.ConnectException failed to connect to /192.168.1.21 port(8080): connect failed: ECONNREFUSED (Connection refused)

i have added the internet permission to the manifest but i don't know what the problem is. can anyone help?

ARSAL AHMED KHAN said...

same question fm my side :
"every time i click connect i get

java.net.ConnectException failed to connect to /192.168.1.21 port(8080): connect failed: ECONNREFUSED (Connection refused)

i have added the internet permission to the manifest but i don't know what the problem is. can anyone help?
"

Unknown said...

after getting connection refused errors i tried this: i shared my phone's internet (mobile data) with my tablet and then tried to connect again and this time it worked! i tried different ports and it works very well. it seems that there is a problem with my router or something that does not let me establish a connection. i hope this works for others like me.

Unknown said...

I am not sure but I think when client disconnect from the application, Toast that writes "removed" does not seem immediately.It is seen after a new client connects and sends a message.How can i handle this? Thanks for the answers.

Best Deals in Israel said...

does this use GCM?

Anonymous said...

showing error java.net.SocketException:socket failed:EACCES(permission denied)....

Erik said...

make sure Add uses-permission of "android.permission.INTERNET" to AndroidManifest.xml.

Unknown said...

hello sir how we can share image over the group and it is possible to make one to one chating as well as make chating over the different network.

Unknown said...

Thanks alot . its best and simplest .....

amg said...

I'm getting this error.
Failed to set EGL_SWAP_BEHAVIOR on surface 0x977f91a0, error=EGL_BAD_MATCH
Skipped 71 frames! The application may be doing too much work on its main thread

Unknown said...

Sir, I'm using your code in our thesis study it Good, but i want to know if the server can interact also to the client.Thanks.

rajesh said...

Is this app requires WIFI/Internet? Please respond.
Thanks

Erik said...

Both client and server have to be inside the same WiFi network.

Shashank said...

Thank you Eric for sharing this application.
I've faced just one problem in this that the ip of the server keeps changing. Can you please tell me if u know a way to handle this frequent ip changing problem.

Unknown said...

I need to, server send real time video from camera instead of text to client. do you know how?

Anonymous said...

I am using esp8266 wifi module.Both the mobile which runs ChatServer and esp8266 are connected to same wifi. After configuring esp8266 module with ip address and port number displayed in ChatServer application, connection is established and data is also sent from esp8266 but it is not displayed on the mobile ChatServer application. Why ? Does this ChatServer application only work on ChatClient application from another mobile and not from any other devices or terminal from pc ?