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:

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

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

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

    ReplyDelete
  4. Hello Kissy Mae Padayjag,

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

    Please check the post updated with video.

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

    kindly reply urgent!!

    ReplyDelete
  6. 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

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

    ReplyDelete
  8. 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

    ReplyDelete
  9. 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

    ReplyDelete
  10. 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

    ReplyDelete
  11. 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?

    ReplyDelete
  12. 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?
    "

    ReplyDelete
  13. 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.

    ReplyDelete
  14. 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.

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

    ReplyDelete
  16. 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.

    ReplyDelete
  17. Thanks alot . its best and simplest .....

    ReplyDelete
  18. 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

    ReplyDelete
  19. 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.

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

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

    ReplyDelete
  22. 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.

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

    ReplyDelete
  24. 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 ?

    ReplyDelete