Tutorials‎ > ‎

Simple Android Chat Application Part 2

posted Nov 14, 2015, 2:12 AM by Kenrick Satrio Sahputra   [ updated Aug 15, 2016, 11:35 PM by Surya Wang ]

Okay, continue from the last tutorial, where we have already setup the GCM stuff, 
now it's time for us to send a downstream message.
  
Downstream message is a message that is sent from our server to GCM Server. 
After that, GCM will figure out where this message should be passed, using token id as it's identifier, then it will send the message to the target device 
But first, our chat application must have a service, that can listen to a new incoming GCM message. 
That's is the overview of how GCM downstream message will work.

But first of all, we need to build our application interface first. For the sake of simplicity, we will only have one View. 
That view will contain 2 input control, 1 button, and 1 list view. 
The first input control will be used as a sender's name, the other will be used as a message content.
The button will be used to send the message.
The list view will contain all chat conversation.

Pretty simple, so let's get started.

1. Change the view in R.layout.activity_main

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<EditText
android:id="@+id/username"
android:hint="Username"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button
android:id="@+id/send_button"
android:text="Send Message"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<EditText
android:id="@+id/message_input"
android:layout_toLeftOf="@id/send_button"
android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<ListView
android:id="@+id/chat_list_container"
android:layout_below="@id/username"
android:layout_above="@id/send_button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</RelativeLayout>
2. Next modify our MainActivity class.
    -. Register a local broadcast receiver so we can handle a new incoming message.
    -. Register button click listener.
    -. Set the cookie storage, so our http request will have the same session id over time.
    -. Set the adapter for our list view, so it can display the chat conversation.
public class MainActivity extends AppCompatActivity {

private EditText mUsernameEt;

private EditText mMessageEt;

private View mSendButton;

private ListView mListView;

private ChatMessageAdapter mAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new ChatMessageAdapter(this);

setContentView(R.layout.activity_main);

prepareView();

mSendButton.setOnClickListener(sendBtnClickListener);
mListView.setAdapter(mAdapter);

// Start a service to register GCM token id to server
Intent intent = new Intent(this, RegistrationIntentService.class);
startService(intent);

// register the local broadcast manager
// to receive message from GCM Listener
LocalBroadcastManager.getInstance(this).registerReceiver(
new ChatReceiver(),
new IntentFilter("message"));

// set the cookie storage, so our http request will have the same sessio id
CookieManager cookieManage = new CookieManager();
CookieHandler.setDefault(cookieManage);
}

private void prepareView() {
mUsernameEt = (EditText) findViewById(R.id.username);
mMessageEt = (EditText) findViewById(R.id.message_input);
mSendButton = findViewById(R.id.send_button);
mListView = (ListView) findViewById(R.id.chat_list_container);
}

private View.OnClickListener sendBtnClickListener = new View.OnClickListener() {

@Override
public void onClick(View v) {
String from = mUsernameEt.getText().toString();
String message = mMessageEt.getText().toString();

// Send a broadcast message
RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);
requestQueue.add(new StringRequest(
Request.Method.GET,
"http://192.168.1.120:8080/AndroidChatServer/broadcast?" +
"from=" + from + "&" +
"message=" + message, null, null));
requestQueue.start();

// Add our own chat to the list
Chat chat = new Chat("You", message, true);
mAdapter.addChatList(chat);

// Scroll the list view to the bottom
mListView.setSelection(mAdapter.getCount() - 1);

// reset input text field
mMessageEt.setText("");
}
};

public class ChatReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
Chat chat = new Chat(extras.getString("from"), extras.getString("message"), false);
mAdapter.addChatList(chat);

// Scroll the list view to the bottom
mListView.setSelection(mAdapter.getCount() - 1);
}
}

}

3. Create a Chat class, that will encapsulate our chat content
public class Chat {

private String from;

private String message;

// will determine if the message is our own message
private boolean isSelf;

public Chat(String from, String message, boolean isSelf) {
this.from = from;
this.message = message;
this.isSelf = isSelf;
}

public String getFrom() {
return from;
}

public String getMessage() {
return message;
}

public boolean isSelf() {
return isSelf;
}
}

4. Create a ChatMessageAdapter class, to display a list view item

public class ChatMessageAdapter extends BaseAdapter {

private List<Chat> chatList;

private Context context;

public ChatMessageAdapter(Context context) {
this.context = context;
this.chatList = new ArrayList<>();
}

@Override
public int getCount() {
return chatList.size();
}

@Override
public Chat getItem(int position) {
return chatList.get(position);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {

if(null == convertView)
convertView = LayoutInflater.from(context).inflate(R.layout.list_chat_view, parent, false);

Chat chat = getItem(position);

TextView fromTv = (TextView) convertView.findViewById(R.id.chat_from_container);
TextView messageTv = (TextView) convertView.findViewById(R.id.chat_message_container);

// Change the alignment of our own message
if(chat.isSelf()) {
fromTv.setGravity(Gravity.END);
messageTv.setGravity(Gravity.END);
} else {
fromTv.setGravity(Gravity.START);
messageTv.setGravity(Gravity.START);
}

fromTv.setText(chat.getFrom());
messageTv.setText(chat.getMessage());

return convertView;
}

@Override
public long getItemId(int position) {
return 0;
}

public void addChatList(Chat chat) {
this.chatList.add(chat);
this.notifyDataSetChanged();
}

}
5. Register a GCM Listener, an object that will be notify, when a new gcm message arrived.

public class MyGCMListenerService extends GcmListenerService {

@Override
public void onMessageReceived(String from, Bundle data) {

Intent intent = new Intent("message");
intent.putExtra("from", data.getString("sender"));
intent.putExtra("message", data.getString("message"));
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
And also don't forget to register the listener service in AndroidManifest.xml
<service
android:name=".MyGCMListenerService"
android:exported="false" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>

So that's it all the code we need for android. Next we will modify our web server so that it can handle Incoming message from Android, 
then it will broadcast to all connected device.

1. Modify register servlet, so that it will saved the tokenId for each session id. 
We will use this session, to guard the broadcast message process, so that it doesn't broadcast it self (sender device).

@WebServlet(urlPatterns = "/register")
public class RegisterTokenServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

String token = req.getParameter("token");

// Add token to application scope
Set<String> tokens = (Set<String>) req.getServletContext().getAttribute("tokens");
if(tokens == null) tokens = new HashSet<>();
tokens.add(token);
req.getServletContext().setAttribute("tokens", tokens);

// Add token to current session scope
req.getSession().setAttribute("token", token);

resp.getWriter().println("Success receive token: " + token);
}

}

2. Create a new BroadcastServlet to receive incoming message and broadcast to all connected device.

@WebServlet(urlPatterns = "/broadcast")
public class BroadcastChatServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

String currentToken = (String) req.getSession().getAttribute("token");

// Get all connected device's token id
Set<String> tokens = (Set<String>) req.getServletContext().getAttribute("tokens");

// Validate so that, if no one have register
// the application not yields an error
if(tokens == null) return;

tokens.forEach(token -> {

// No need to send GCM Message to our own self
if(token.equals(currentToken)) return;

ResteasyClient client = new ResteasyClientBuilder().build();
client.register((ClientRequestFilter) request -> {
request.getHeaders().add("Content-Type", "application/json");
request.getHeaders().add("Authorization", "key=AIzaSyBUwoBTcSHP7xvFrG3cx6mwJPhj9g84A3o");
});
ResteasyWebTarget target = client.target("https://gcm-http.googleapis.com/gcm/send");

Map<String, Object> body = new HashMap<>();
Map<String, String> data = new HashMap<>();

String from = req.getParameter("from");
if(from == null || "".equals(from))
from = "Anonymous";

data.put("sender", from);
data.put("message", req.getParameter("message"));

body.put("to", token);
body.put("data", data); target.request().post(Entity.json(body));

});
}
}

That's it, pretty simple. I hope you guys understand the bold point here, and you can development your own realtime application.