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.


21 comments:

Pushpal Roy 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?

Andr.oid Eric 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!

Andr.oid Eric 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!!

Sridhar Kulkarni 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

Erfan Gholampour 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 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?
"

Erfan Gholampour 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.

Oğuz Artiran 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.

Avi Parshan said...

does this use GCM?

Anonymous said...

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

Andr.oid Eric said...

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

Resilience soft 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.

Hitesh yadav 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