In the 1st & 2nd parts of this article, we have built the server app including the REST API and admin panel. We also covered the initializing a project with gcm integrated with few primary tests.

This part covers a realtime scenario of GCM usage by providing a crucial information like performing dynamic actions like updating UI depending on type of the notification. The actions like incrementing the message counter in the list, automatically appearing the messages in chat thread is explained.

 

Building Realtime Chat App

The chat app mainly contains three screens. First is Login Screen where the user will be prompted to enter name and email. Second screen is Chat Rooms list screen where list of chat rooms. The third screen displays the discussion of a single chat room where the discussion messages will be aligned left and right.

google-gcm-notification

Adding Volley Support

We use android volley library to make all the http requests to rest api endpoints. Volley provides an easy way to handle the http requests and responses.

  1. 1. Opengradlelocated under app directory and add volley dependency and rebuild the project.
build.gradle
dependencies {

compile ‘com.mcxiaoke.volley:library-aar:1.0.0’

}

  1. 2. Openjavalocated under app package and modify the code as below. Here I am creating singleton instance of volley RequestQueue.
MyApplication.java
package info.androidhive.gcm.app;

 

/**

* Created by Lincoln on 14/10/15.

*/

 

import android.app.Application;

import android.content.Intent;

import android.text.TextUtils;

 

import com.android.volley.Request;

import com.android.volley.RequestQueue;

import com.android.volley.toolbox.Volley;

 

import info.androidhive.gcm.activity.LoginActivity;

import info.androidhive.gcm.helper.MyPreferenceManager;

 

/**

* Created by Ravi on 13/05/15.

*/

 

public class MyApplication extends Application {

 

public static final String TAG = MyApplication.class

.getSimpleName();

 

private RequestQueue mRequestQueue;

 

private static MyApplication mInstance;

 

private MyPreferenceManager pref;

 

@Override

public void onCreate() {

super.onCreate();

mInstance = this;

}

 

public static synchronized MyApplication getInstance() {

return mInstance;

}

 

public RequestQueue getRequestQueue() {

if (mRequestQueue == null) {

mRequestQueue = Volley.newRequestQueue(getApplicationContext());

}

 

return mRequestQueue;

}

 

public MyPreferenceManager getPrefManager() {

if (pref == null) {

pref = new MyPreferenceManager(this);

}

 

return pref;

}

 

public <T> void addToRequestQueue(Request<T> req, String tag) {

req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);

getRequestQueue().add(req);

}

 

public <T> void addToRequestQueue(Request<T> req) {

req.setTag(TAG);

getRequestQueue().add(req);

}

 

public void cancelPendingRequests(Object tag) {

if (mRequestQueue != null) {

mRequestQueue.cancelAll(tag);

}

}

 

public void logout() {

pref.clear();

Intent intent = new Intent(this, LoginActivity.class);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

startActivity(intent);

}

}

  1. 3. Create a package namedmodel. Insidemodel package, create three classes named javaMessage.javaand ChatRoom.java. Theses classes will be used to create objects while parsing the json responses. You can also notice that, these classes implements Serializable which allows us to pass objects to an activity using intents.
User.java
package info.androidhive.gcm.model;

 

import java.io.Serializable;

 

public class User implements Serializable {

String id, name, email;

 

public User() {

}

 

public User(String id, String name, String email) {

this.id = id;

this.name = name;

this.email = email;

}

 

public String getId() {

return id;

}

 

public void setId(String id) {

this.id = id;

}

 

public String getName() {

return name;

}

 

public void setName(String name) {

this.name = name;

}

 

public String getEmail() {

return email;

}

 

public void setEmail(String email) {

this.email = email;

}

}

Message.java
package info.androidhive.gcm.model;

 

import java.io.Serializable;

 

public class Message implements Serializable {

String id, message, createdAt;

User user;

 

public Message() {

}

 

public Message(String id, String message, String createdAt, User user) {

this.id = id;

this.message = message;

this.createdAt = createdAt;

this.user = user;

}

 

public String getId() {

return id;

}

 

public void setId(String id) {

this.id = id;

}

 

public String getMessage() {

return message;

}

 

public void setMessage(String message) {

this.message = message;

}

 

public String getCreatedAt() {

return createdAt;

}

 

public void setCreatedAt(String createdAt) {

this.createdAt = createdAt;

}

 

public User getUser() {

return user;

}

 

public void setUser(User user) {

this.user = user;

}

}

 

ChatRoom.java
package info.androidhive.gcm.model;

 

import java.io.Serializable;

 

public class ChatRoom implements Serializable {

String id, name, lastMessage, timestamp;

int unreadCount;

 

public ChatRoom() {

}

 

public ChatRoom(String id, String name, String lastMessage, String timestamp, int unreadCount) {

this.id = id;

this.name = name;

this.lastMessage = lastMessage;

this.timestamp = timestamp;

this.unreadCount = unreadCount;

}

 

public String getId() {

return id;

}

 

public void setId(String id) {

this.id = id;

}

 

public String getName() {

return name;

}

 

public void setName(String name) {

this.name = name;

}

 

public String getLastMessage() {

return lastMessage;

}

 

public void setLastMessage(String lastMessage) {

this.lastMessage = lastMessage;

}

 

public int getUnreadCount() {

return unreadCount;

}

 

public void setUnreadCount(int unreadCount) {

this.unreadCount = unreadCount;

}

 

public String getTimestamp() {

return timestamp;

}

 

public void setTimestamp(String timestamp) {

this.timestamp = timestamp;

}

}

  1. 4. Openjavaand add storeUser() and getUser() methods which stores the user information in shared preferences. These methods will be called once the user successfully logged in.
MyPreferenceManager.java
package info.androidhive.gcm.helper;

 

import android.content.Context;

import android.content.SharedPreferences;

import android.util.Log;

 

import info.androidhive.gcm.model.User;

 

/**

* Created by Lincoln on 07/01/16.

*/

public class MyPreferenceManager {

 

private String TAG = MyPreferenceManager.class.getSimpleName();

 

// Shared Preferences

SharedPreferences pref;

 

// Editor for Shared preferences

SharedPreferences.Editor editor;

 

// Context

Context _context;

 

// Shared pref mode

int PRIVATE_MODE = 0;

 

// Sharedpref file name

private static final String PREF_NAME = “androidhive_gcm”;

 

// All Shared Preferences Keys

private static final String KEY_USER_ID = “user_id”;

private static final String KEY_USER_NAME = “user_name”;

private static final String KEY_USER_EMAIL = “user_email”;

private static final String KEY_NOTIFICATIONS = “notifications”;

 

// Constructor

public MyPreferenceManager(Context context) {

this._context = context;

pref = _context.getSharedPreferences(PREF_NAME, PRIVATE_MODE);

editor = pref.edit();

}

 

 

public void storeUser(User user) {

editor.putString(KEY_USER_ID, user.getId());

editor.putString(KEY_USER_NAME, user.getName());

editor.putString(KEY_USER_EMAIL, user.getEmail());

editor.commit();

 

Log.e(TAG, “User is stored in shared preferences. ” + user.getName() + “, ” + user.getEmail());

}

 

public User getUser() {

if (pref.getString(KEY_USER_ID, null) != null) {

String id, name, email;

id = pref.getString(KEY_USER_ID, null);

name = pref.getString(KEY_USER_NAME, null);

email = pref.getString(KEY_USER_EMAIL, null);

 

User user = new User(id, name, email);

return user;

}

return null;

}

 

public void addNotification(String notification) {

 

// get old notifications

String oldNotifications = getNotifications();

 

if (oldNotifications != null) {

oldNotifications += “|” + notification;

} else {

oldNotifications = notification;

}

 

editor.putString(KEY_NOTIFICATIONS, oldNotifications);

editor.commit();

}

 

public String getNotifications() {

return pref.getString(KEY_NOTIFICATIONS, null);

}

 

public void clear() {

editor.clear();

editor.commit();

}

}

  1. 5. Createjavain app package. Here we declare the REST API endpoint urls. If you are testing the app on localhost, use the correct Ip address of the computer on which php services are running.
EndPoints.java
package info.androidhive.gcm.app;

 

public class EndPoints {

 

// localhost url –

public static final String BASE_URL = “http://192.168.0.101/gcm_chat/v1“;

public static final String LOGIN = BASE_URL + “/user/login”;

public static final String USER = BASE_URL + “/user/_ID_”;

public static final String CHAT_ROOMS = BASE_URL + “/chat_rooms”;

public static final String CHAT_THREAD = BASE_URL + “/chat_rooms/_ID_”;

public static final String CHAT_ROOM_MESSAGE = BASE_URL + “/chat_rooms/_ID_/message”;

}

  1. 6. Openjavalocated under gcm package and modify the code as below. Here I have modified the code sendRegistrationToServer() method to send the gcm registration token to our server to update it inMySQL database.
package info.androidhive.gcm.gcm;

 

import android.app.IntentService;

import android.content.Intent;

import android.content.SharedPreferences;

import android.preference.PreferenceManager;

import android.support.v4.content.LocalBroadcastManager;

import android.util.Log;

import android.widget.Toast;

 

import com.android.volley.NetworkResponse;

import com.android.volley.Request;

import com.android.volley.Response;

import com.android.volley.VolleyError;

import com.android.volley.toolbox.StringRequest;

import com.google.android.gms.gcm.GcmPubSub;

import com.google.android.gms.gcm.GoogleCloudMessaging;

import com.google.android.gms.iid.InstanceID;

 

import org.json.JSONException;

import org.json.JSONObject;

 

import java.io.IOException;

import java.util.HashMap;

import java.util.Map;

 

import info.androidhive.gcm.R;

import info.androidhive.gcm.app.Config;

import info.androidhive.gcm.app.EndPoints;

import info.androidhive.gcm.app.MyApplication;

import info.androidhive.gcm.model.User;

 

public class GcmIntentService extends IntentService {

 

private static final String TAG = GcmIntentService.class.getSimpleName();

 

public GcmIntentService() {

super(TAG);

}

 

public static final String KEY = “key”;

public static final String TOPIC = “topic”;

public static final String SUBSCRIBE = “subscribe”;

public static final String UNSUBSCRIBE = “unsubscribe”;

 

 

@Override

protected void onHandleIntent(Intent intent) {

String key = intent.getStringExtra(KEY);

switch (key) {

case SUBSCRIBE:

// subscribe to a topic

String topic = intent.getStringExtra(TOPIC);

subscribeToTopic(topic);

break;

case UNSUBSCRIBE:

break;

default:

// if key is specified, register with GCM

registerGCM();

}

 

}

 

/**

* Registering with GCM and obtaining the gcm registration id

*/

private void registerGCM() {

SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);

 

try {

InstanceID instanceID = InstanceID.getInstance(this);

String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),

GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);

 

Log.e(TAG, “GCM Registration Token: ” + token);

 

// sending the registration id to our server

sendRegistrationToServer(token);

 

sharedPreferences.edit().putBoolean(Config.SENT_TOKEN_TO_SERVER, true).apply();

} catch (Exception e) {

Log.e(TAG, “Failed to complete token refresh”, e);

 

sharedPreferences.edit().putBoolean(Config.SENT_TOKEN_TO_SERVER, false).apply();

}

// Notify UI that registration has completed, so the progress indicator can be hidden.

Intent registrationComplete = new Intent(Config.REGISTRATION_COMPLETE);

LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);

}

 

private void sendRegistrationToServer(final String token) {

 

// checking for valid login session

User user = MyApplication.getInstance().getPrefManager().getUser();

if (user == null) {

// TODO

// user not found, redirecting him to login screen

return;

}

 

String endPoint = EndPoints.USER.replace(“_ID_”, user.getId());

 

Log.e(TAG, “endpoint: ” + endPoint);

 

StringRequest strReq = new StringRequest(Request.Method.PUT,

endPoint, new Response.Listener<String>() {

 

@Override

public void onResponse(String response) {

Log.e(TAG, “response: ” + response);

 

try {

JSONObject obj = new JSONObject(response);

 

// check for error

if (obj.getBoolean(“error”) == false) {

// broadcasting token sent to server

Intent registrationComplete = new Intent(Config.SENT_TOKEN_TO_SERVER);

LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(registrationComplete);

} else {

Toast.makeText(getApplicationContext(), “Unable to send gcm registration id to our sever. ” + obj.getJSONObject(“error”).getString(“message”), Toast.LENGTH_LONG).show();

}

 

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “Json parse error: ” + e.getMessage(), Toast.LENGTH_SHORT).show();

}

}

}, new Response.ErrorListener() {

 

@Override

public void onErrorResponse(VolleyError error) {

NetworkResponse networkResponse = error.networkResponse;

Log.e(TAG, “Volley error: ” + error.getMessage() + “, code: ” + networkResponse);

Toast.makeText(getApplicationContext(), “Volley error: ” + error.getMessage(), Toast.LENGTH_SHORT).show();

}

}) {

 

@Override

protected Map<String, String> getParams() {

Map<String, String> params = new HashMap<String, String>();

params.put(“gcm_registration_id”, token);

 

Log.e(TAG, “params: ” + params.toString());

return params;

}

};

 

//Adding request to request queue

MyApplication.getInstance().addToRequestQueue(strReq);

}

 

/**

* Subscribe to a topic

*/

public static void subscribeToTopic(String topic) {

GcmPubSub pubSub = GcmPubSub.getInstance(MyApplication.getInstance().getApplicationContext());

InstanceID instanceID = InstanceID.getInstance(MyApplication.getInstance().getApplicationContext());

String token = null;

try {

token = instanceID.getToken(MyApplication.getInstance().getApplicationContext().getString(R.string.gcm_defaultSenderId),

GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);

if (token != null) {

pubSub.subscribe(token, “/topics/” + topic, null);

Log.e(TAG, “Subscribed to topic: ” + topic);

} else {

Log.e(TAG, “error: gcm registration id is null”);

}

} catch (IOException e) {

Log.e(TAG, “Topic subscribe error. Topic: ” + topic + “, error: ” + e.getMessage());

Toast.makeText(MyApplication.getInstance().getApplicationContext(), “Topic subscribe error. Topic: ” + topic + “, error: ” + e.getMessage(), Toast.LENGTH_SHORT).show();

}

}

 

public void unsubscribeFromTopic(String topic) {

GcmPubSub pubSub = GcmPubSub.getInstance(getApplicationContext());

InstanceID instanceID = InstanceID.getInstance(getApplicationContext());

String token = null;

try {

token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),

GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);

if (token != null) {

pubSub.unsubscribe(token, “”);

Log.e(TAG, “Unsubscribed from topic: ” + topic);

} else {

Log.e(TAG, “error: gcm registration id is null”);

}

} catch (IOException e) {

Log.e(TAG, “Topic unsubscribe error. Topic: ” + topic + “, error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “Topic subscribe error. Topic: ” + topic + “, error: ” + e.getMessage(), Toast.LENGTH_SHORT).show();

}

}

}

Adding Login Screen

With all the required files in place, we’ll add the first app screen i.e login screen. Login screen is added for the purpose of collecting user’s name and email. Note that this is not a proper authentication method as it won’t requires the password to be entered. Any user can login into our app by entering any random name and email.

  1. 7. Openxmllocated under values and add the below string values.
strings.xml
<resources>

<string name=”app_name”>Google Cloud Messaging</string>

<string name=”action_settings”>Settings</string>

<string name=”title_activity_login”>LoginActivity</string>

<string name=”hint_name”>Full Name</string>

<string name=”hint_email”>Email</string>

<string name=”err_msg_name”>Enter full name</string>

<string name=”err_msg_email”>Enter valid email address</string>

<string name=”title_activity_chat_room_discussion”>ChatRoomDiscussionActivity</string>

<string name=”action_logout”>Logout</string>

</resources>

  1. 8. Openxmland add below color values.
colors.xml
<?xml version=”1.0″ encoding=”utf-8″?>

<resources>

<color name=”colorPrimary”>#7B1FA2</color>

<color name=”colorPrimaryDark”>#6A1B9A</color>

<color name=”colorAccent”>#EA80FC</color>

<color name=”list_divider”>#dedede</color>

<color name=”bg_bubble_self”>#E1E1E1</color>

</resources>

  1. 9. Create an activity namedjavaby right clicking on activity New Activity Blank Activity. This also creates activity_login.xml and content_login.xml layout files for login activity.
  2. 10. Openxmland modify the layout as below. Here I have added the appbar and a toolbar.
activity_login.xml
<?xml version=”1.0″ encoding=”utf-8″?>

<android.support.design.widget.CoordinatorLayout 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”

android:fitsSystemWindows=”true”

tools:context=”info.androidhive.gcm.activity.LoginActivity”>

 

<android.support.design.widget.AppBarLayout

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:theme=”@style/AppTheme.AppBarOverlay”>

 

<android.support.v7.widget.Toolbar

android:id=”@+id/toolbar”

android:layout_width=”match_parent”

android:layout_height=”?attr/actionBarSize”

android:background=”?attr/colorPrimary”

app:popupTheme=”@style/AppTheme.PopupOverlay” />

 

</android.support.design.widget.AppBarLayout>

 

<include layout=”@layout/content_login” />

 

</android.support.design.widget.CoordinatorLayout>

  1. 11. Openxmland create a simple login form with name and email input fields.
activity_login.xml
<?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”

android:paddingBottom=”@dimen/activity_vertical_margin”

android:paddingLeft=”@dimen/activity_horizontal_margin”

android:paddingRight=”@dimen/activity_horizontal_margin”

android:paddingTop=”@dimen/activity_vertical_margin”

app:layout_behavior=”@string/appbar_scrolling_view_behavior”

tools:context=”info.androidhive.gcm.activity.LoginActivity”

tools:showIn=”@layout/activity_login”>

 

<LinearLayout

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:orientation=”vertical”>

 

<android.support.design.widget.TextInputLayout

android:id=”@+id/input_layout_name”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”>

 

<EditText

android:id=”@+id/input_name”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:hint=”@string/hint_name” />

 

</android.support.design.widget.TextInputLayout>

 

<android.support.design.widget.TextInputLayout

android:id=”@+id/input_layout_email”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”>

 

<EditText

android:id=”@+id/input_email”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:hint=”@string/hint_email” />

 

</android.support.design.widget.TextInputLayout>

 

<Button android:id=”@+id/btn_enter”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:text=”Enter” />

 

</LinearLayout>

 

<ProgressBar

android:id=”@+id/progressBar”

android:layout_width=”30dp”

android:layout_height=”30dp”

android:layout_alignParentBottom=”true”

android:layout_centerHorizontal=”true”

android:layout_gravity=”center”

android:layout_marginBottom=”10dp”

android:indeterminateTint=”@color/colorAccent”

android:visibility=”gone”

android:indeterminateTintMode=”src_atop” />

 

</RelativeLayout>

  1. 12. Openjavaand paste the below code. Here

> Before setting the contentView, we’ll check for user session in shared preferences. If the user is already logged in, the MainActivity will be launched.

> login() method makes an http request to login endpoint by passing name and email as post parameters. On the server a new user will be created and the json response will be served.

> After parsing the json, user session will be created by storing the user object in shared preferences andMainActivity will be launched.

LoginActivity.java
package info.androidhive.gcm.activity;

 

import android.content.Intent;

import android.os.Bundle;

import android.support.design.widget.TextInputLayout;

import android.support.v7.app.AppCompatActivity;

import android.support.v7.widget.Toolbar;

import android.text.Editable;

import android.text.TextUtils;

import android.text.TextWatcher;

import android.util.Log;

import android.view.View;

import android.view.WindowManager;

import android.widget.Button;

import android.widget.EditText;

import android.widget.Toast;

 

import com.android.volley.NetworkResponse;

import com.android.volley.Request;

import com.android.volley.Response;

import com.android.volley.VolleyError;

import com.android.volley.toolbox.StringRequest;

 

import org.json.JSONException;

import org.json.JSONObject;

 

import java.util.HashMap;

import java.util.Map;

 

import info.androidhive.gcm.R;

import info.androidhive.gcm.app.EndPoints;

import info.androidhive.gcm.app.MyApplication;

import info.androidhive.gcm.model.User;

 

public class LoginActivity extends AppCompatActivity {

 

private String TAG = LoginActivity.class.getSimpleName();

private EditText inputName, inputEmail;

private TextInputLayout inputLayoutName, inputLayoutEmail;

private Button btnEnter;

 

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

 

/**

* Check for login session. It user is already logged in

* redirect him to main activity

* */

if (MyApplication.getInstance().getPrefManager().getUser() != null) {

startActivity(new Intent(this, MainActivity.class));

finish();

}

 

setContentView(R.layout.activity_login);

 

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

setSupportActionBar(toolbar);

inputLayoutName = (TextInputLayout) findViewById(R.id.input_layout_name);

inputLayoutEmail = (TextInputLayout) findViewById(R.id.input_layout_email);

inputName = (EditText) findViewById(R.id.input_name);

inputEmail = (EditText) findViewById(R.id.input_email);

btnEnter = (Button) findViewById(R.id.btn_enter);

 

inputName.addTextChangedListener(new MyTextWatcher(inputName));

inputEmail.addTextChangedListener(new MyTextWatcher(inputEmail));

 

btnEnter.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

login();

}

});

}

 

/**

* logging in user. Will make http post request with name, email

* as parameters

*/

private void login() {

if (!validateName()) {

return;

}

 

if (!validateEmail()) {

return;

}

 

final String name = inputName.getText().toString();

final String email = inputEmail.getText().toString();

 

StringRequest strReq = new StringRequest(Request.Method.POST,

EndPoints.LOGIN, new Response.Listener<String>() {

 

@Override

public void onResponse(String response) {

Log.e(TAG, “response: ” + response);

 

try {

JSONObject obj = new JSONObject(response);

 

// check for error flag

if (obj.getBoolean(“error”) == false) {

// user successfully logged in

 

JSONObject userObj = obj.getJSONObject(“user”);

User user = new User(userObj.getString(“user_id”),

userObj.getString(“name”),

userObj.getString(“email”));

 

// storing user in shared preferences

MyApplication.getInstance().getPrefManager().storeUser(user);

 

// start main activity

startActivity(new Intent(getApplicationContext(), MainActivity.class));

finish();

 

} else {

// login error – simply toast the message

Toast.makeText(getApplicationContext(), “” + obj.getJSONObject(“error”).getString(“message”), Toast.LENGTH_LONG).show();

}

 

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “Json parse error: ” + e.getMessage(), Toast.LENGTH_SHORT).show();

}

}

}, new Response.ErrorListener() {

 

@Override

public void onErrorResponse(VolleyError error) {

NetworkResponse networkResponse = error.networkResponse;

Log.e(TAG, “Volley error: ” + error.getMessage() + “, code: ” + networkResponse);

Toast.makeText(getApplicationContext(), “Volley error: ” + error.getMessage(), Toast.LENGTH_SHORT).show();

}

}) {

 

@Override

protected Map<String, String> getParams() {

Map<String, String> params = new HashMap<>();

params.put(“name”, name);

params.put(“email”, email);

 

Log.e(TAG, “params: ” + params.toString());

return params;

}

};

 

//Adding request to request queue

MyApplication.getInstance().addToRequestQueue(strReq);

}

 

private void requestFocus(View view) {

if (view.requestFocus()) {

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);

}

}

 

// Validating name

private boolean validateName() {

if (inputName.getText().toString().trim().isEmpty()) {

inputLayoutName.setError(getString(R.string.err_msg_name));

requestFocus(inputName);

return false;

} else {

inputLayoutName.setErrorEnabled(false);

}

 

return true;

}

 

// Validating email

private boolean validateEmail() {

String email = inputEmail.getText().toString().trim();

 

if (email.isEmpty() || !isValidEmail(email)) {

inputLayoutEmail.setError(getString(R.string.err_msg_email));

requestFocus(inputEmail);

return false;

} else {

inputLayoutEmail.setErrorEnabled(false);

}

 

return true;

}

 

private static boolean isValidEmail(String email) {

return !TextUtils.isEmpty(email) && android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches();

}

 

private class MyTextWatcher implements TextWatcher {

 

private View view;

private MyTextWatcher(View view) {

this.view = view;

}

 

public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

}

 

public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

}

 

public void afterTextChanged(Editable editable) {

switch (view.getId()) {

case R.id.input_name:

validateName();

break;

case R.id.input_email:

validateEmail();

break;

}

}

}

}

  1. 13. Openxmland make LoginActivity as Launcher activity.

Now run the app and verify the rest api call by submitting the form.

android-realtime-chat-app-login-screen

Adding Chat Rooms List Screen

The chat rooms will be displayed in a list fashion for which we use RecyclerView. So add the recycler view support to build.gradle.

  1. 14. Open gradleand add the RecyclerViewdependency and rebuild the project.

compile ‘com.android.support:recyclerview-v7:23.1.1’

Below are the final dependencies of my build.gradle

build.gradle
dependencies {

compile fileTree(dir: ‘libs’, include: [‘*.jar’])

testCompile ‘junit:junit:4.12’

compile ‘com.android.support:appcompat-v7:23.1.1’

compile ‘com.android.support:design:23.1.1’

compile ‘com.mcxiaoke.volley:library-aar:1.0.0’

compile ‘com.android.support:recyclerview-v7:23.1.1’

compile “com.google.android.gms:play-services:8.3.0”

}

  1. 15. Create a layout named xml. This layout renders a single chat room information like chat room namelast messageunread message countand the timestampin the recycler view.
chat_rooms_list_row
<?xml version=”1.0″ encoding=”utf-8″?>

<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android

android:layout_width=”match_parent”

android:layout_height=”match_parent”

android:orientation=”vertical”

android:paddingBottom=”16dp”

android:paddingLeft=”16dp”

android:paddingRight=”16dp”

android:paddingTop=”16dp”>

 

<TextView

android:id=”@+id/name”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:layout_alignParentTop=”true”

android:textColor=”#444444″

android:textStyle=”bold”

android:textSize=”16dp” />

 

<TextView android:id=”@+id/message”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:layout_below=”@id/name”

android:textColor=”#888888″

android:layout_marginTop=”5dp”

android:text=”Seems gcm will take some time”/>

 

<TextView android:id=”@+id/timestamp”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:text=”12:00 AM”

android:textSize=”10dp”

android:layout_alignParentRight=”true”

android:layout_alignParentTop=”true”/>

 

<TextView android:id=”@+id/count”

android:layout_width=”20dp”

android:layout_height=”20dp”

android:gravity=”center”

android:textSize=”10dp”

android:textColor=”@android:color/white”

android:layout_below=”@id/timestamp”

android:layout_marginTop=”5dp”

android:layout_alignParentRight=”true”

android:text=”5″

android:background=”@drawable/bg_circle”/>

 

</RelativeLayout>

  1. 16. Create a new package named adapter. Under adapter create a class named java. This is a recycler view adapter which provides data to chat rooms list view.
ChatRoomsAdapter.java
package info.androidhive.gcm.adapter;

 

import android.content.Context;

import android.support.v7.widget.RecyclerView;

import android.view.GestureDetector;

import android.view.LayoutInflater;

import android.view.MotionEvent;

import android.view.View;

import android.view.ViewGroup;

import android.widget.TextView;

 

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Calendar;

import java.util.Date;

 

import info.androidhive.gcm.R;

import info.androidhive.gcm.model.ChatRoom;

 

 

public class ChatRoomsAdapter extends RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder> {

 

private Context mContext;

private ArrayList<ChatRoom> chatRoomArrayList;

private static String today;

 

public class ViewHolder extends RecyclerView.ViewHolder {

public TextView name, message, timestamp, count;

 

public ViewHolder(View view) {

super(view);

name = (TextView) view.findViewById(R.id.name);

message = (TextView) view.findViewById(R.id.message);

timestamp = (TextView) view.findViewById(R.id.timestamp);

count = (TextView) view.findViewById(R.id.count);

}

}

 

 

public ChatRoomsAdapter(Context mContext, ArrayList<ChatRoom> chatRoomArrayList) {

this.mContext = mContext;

this.chatRoomArrayList = chatRoomArrayList;

 

Calendar calendar = Calendar.getInstance();

today = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));

}

 

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

View itemView = LayoutInflater.from(parent.getContext())

.inflate(R.layout.chat_rooms_list_row, parent, false);

 

return new ViewHolder(itemView);

}

 

@Override

public void onBindViewHolder(ViewHolder holder, int position) {

ChatRoom chatRoom = chatRoomArrayList.get(position);

holder.name.setText(chatRoom.getName());

holder.message.setText(chatRoom.getLastMessage());

if (chatRoom.getUnreadCount() > 0) {

holder.count.setText(String.valueOf(chatRoom.getUnreadCount()));

holder.count.setVisibility(View.VISIBLE);

} else {

holder.count.setVisibility(View.GONE);

}

 

holder.timestamp.setText(getTimeStamp(chatRoom.getTimestamp()));

}

 

@Override

public int getItemCount() {

return chatRoomArrayList.size();

}

 

public static String getTimeStamp(String dateStr) {

SimpleDateFormat format = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

String timestamp = “”;

 

today = today.length() < 2 ? “0” + today : today;

 

try {

Date date = format.parse(dateStr);

SimpleDateFormat todayFormat = new SimpleDateFormat(“dd”);

String dateToday = todayFormat.format(date);

format = dateToday.equals(today) ? new SimpleDateFormat(“hh:mm a”) : new SimpleDateFormat(“dd LLL, hh:mm a”);

String date1 = format.format(date);

timestamp = date1.toString();

} catch (ParseException e) {

e.printStackTrace();

}

 

return timestamp;

}

 

public interface ClickListener {

void onClick(View view, int position);

 

void onLongClick(View view, int position);

}

 

public static class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {

 

private GestureDetector gestureDetector;

private ChatRoomsAdapter.ClickListener clickListener;

 

public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ChatRoomsAdapter.ClickListener clickListener) {

this.clickListener = clickListener;

gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {

@Override

public boolean onSingleTapUp(MotionEvent e) {

return true;

}

 

@Override

public void onLongPress(MotionEvent e) {

View child = recyclerView.findChildViewUnder(e.getX(), e.getY());

if (child != null && clickListener != null) {

clickListener.onLongClick(child, recyclerView.getChildPosition(child));

}

}

});

}

 

@Override

public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

 

View child = rv.findChildViewUnder(e.getX(), e.getY());

if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {

clickListener.onClick(child, rv.getChildPosition(child));

}

return false;

}

 

@Override

public void onTouchEvent(RecyclerView rv, MotionEvent e) {

}

 

@Override

public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

 

}

}

}

  1. 17. Open javaand modify the code as below. Here I have added few methods to handle push notification message. First the push notification will be identified by flagnode and then appropriate action will be taken.

> processChatRoomPush() method will be called whenever chat room push message is received

> processUserMessage() will be called when the push is specific to logged in user.

MyGcmPushReceiver.java
package info.androidhive.gcm.gcm;

 

import android.content.Context;

import android.content.Intent;

import android.os.Bundle;

import android.support.v4.content.LocalBroadcastManager;

import android.text.TextUtils;

import android.util.Log;

import android.widget.Toast;

 

import com.google.android.gms.gcm.GcmListenerService;

 

import org.json.JSONException;

import org.json.JSONObject;

 

import info.androidhive.gcm.activity.ChatRoomActivity;

import info.androidhive.gcm.activity.MainActivity;

import info.androidhive.gcm.app.Config;

import info.androidhive.gcm.app.MyApplication;

import info.androidhive.gcm.model.Message;

import info.androidhive.gcm.model.User;

 

public class MyGcmPushReceiver extends GcmListenerService {

 

private static final String TAG = MyGcmPushReceiver.class.getSimpleName();

 

private NotificationUtils notificationUtils;

 

/**

* Called when message is received.

*

* @param from   SenderID of the sender.

* @param bundle Data bundle containing message data as key/value pairs.

*               For Set of keys use data.keySet().

*/

 

@Override

public void onMessageReceived(String from, Bundle bundle) {

String title = bundle.getString(“title”);

Boolean isBackground = Boolean.valueOf(bundle.getString(“is_background”));

String flag = bundle.getString(“flag”);

String data = bundle.getString(“data”);

Log.d(TAG, “From: ” + from);

Log.d(TAG, “title: ” + title);

Log.d(TAG, “isBackground: ” + isBackground);

Log.d(TAG, “flag: ” + flag);

Log.d(TAG, “data: ” + data);

 

if (flag == null)

return;

 

if(MyApplication.getInstance().getPrefManager().getUser() == null){

// user is not logged in, skipping push notification

Log.e(TAG, “user is not logged in, skipping push notification”);

return;

}

 

if (from.startsWith(“/topics/”)) {

// message received from some topic.

} else {

// normal downstream message.

}

 

switch (Integer.parseInt(flag)) {

case Config.PUSH_TYPE_CHATROOM:

// push notification belongs to a chat room

processChatRoomPush(title, isBackground, data);

break;

case Config.PUSH_TYPE_USER:

// push notification is specific to user

processUserMessage(title, isBackground, data);

break;

}

}

 

/**

* Processing chat room push message

* this message will be broadcasts to all the activities registered

* */

private void processChatRoomPush(String title, boolean isBackground, String data) {

if (!isBackground) {

 

try {

JSONObject datObj = new JSONObject(data);

 

String chatRoomId = datObj.getString(“chat_room_id”);

 

JSONObject mObj = datObj.getJSONObject(“message”);

Message message = new Message();

message.setMessage(mObj.getString(“message”));

message.setId(mObj.getString(“message_id”));

message.setCreatedAt(mObj.getString(“created_at”));

 

JSONObject uObj = datObj.getJSONObject(“user”);

 

// skip the message if the message belongs to same user as

// the user would be having the same message when he was sending

// but it might differs in your scenario

if (uObj.getString(“user_id”).equals(MyApplication.getInstance().getPrefManager().getUser().getId())) {

Log.e(TAG, “Skipping the push message as it belongs to same user”);

return;

}

 

User user = new User();

user.setId(uObj.getString(“user_id”));

user.setEmail(uObj.getString(“email”));

user.setName(uObj.getString(“name”));

message.setUser(user);

 

// verifying whether the app is in background or foreground

if (!NotificationUtils.isAppIsInBackground(getApplicationContext())) {

 

// app is in foreground, broadcast the push message

Intent pushNotification = new Intent(Config.PUSH_NOTIFICATION);

pushNotification.putExtra(“type”, Config.PUSH_TYPE_CHATROOM);

pushNotification.putExtra(“message”, message);

pushNotification.putExtra(“chat_room_id”, chatRoomId);

LocalBroadcastManager.getInstance(this).sendBroadcast(pushNotification);

 

// play notification sound

NotificationUtils notificationUtils = new NotificationUtils();

notificationUtils.playNotificationSound();

} else {

 

// app is in background. show the message in notification try

Intent resultIntent = new Intent(getApplicationContext(), ChatRoomActivity.class);

resultIntent.putExtra(“chat_room_id”, chatRoomId);

showNotificationMessage(getApplicationContext(), title, user.getName() + ” : ” + message.getMessage(), message.getCreatedAt(), resultIntent);

}

 

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “Json parse error: ” + e.getMessage(), Toast.LENGTH_LONG).show();

}

 

} else {

// the push notification is silent, may be other operations needed

// like inserting it in to SQLite

}

}

 

/**

* Processing user specific push message

* It will be displayed with / without image in push notification tray

* */

private void processUserMessage(String title, boolean isBackground, String data) {

if (!isBackground) {

 

try {

JSONObject datObj = new JSONObject(data);

 

String imageUrl = datObj.getString(“image”);

 

JSONObject mObj = datObj.getJSONObject(“message”);

Message message = new Message();

message.setMessage(mObj.getString(“message”));

message.setId(mObj.getString(“message_id”));

message.setCreatedAt(mObj.getString(“created_at”));

 

JSONObject uObj = datObj.getJSONObject(“user”);

User user = new User();

user.setId(uObj.getString(“user_id”));

user.setEmail(uObj.getString(“email”));

user.setName(uObj.getString(“name”));

message.setUser(user);

 

// verifying whether the app is in background or foreground

if (!NotificationUtils.isAppIsInBackground(getApplicationContext())) {

 

// app is in foreground, broadcast the push message

Intent pushNotification = new Intent(Config.PUSH_NOTIFICATION);

pushNotification.putExtra(“type”, Config.PUSH_TYPE_USER);

pushNotification.putExtra(“message”, message);

LocalBroadcastManager.getInstance(this).sendBroadcast(pushNotification);

 

// play notification sound

NotificationUtils notificationUtils = new NotificationUtils();

notificationUtils.playNotificationSound();

} else {

 

// app is in background. show the message in notification try

Intent resultIntent = new Intent(getApplicationContext(), MainActivity.class);

 

// check for push notification image attachment

if (TextUtils.isEmpty(imageUrl)) {

showNotificationMessage(getApplicationContext(), title, user.getName() + ” : ” + message.getMessage(), message.getCreatedAt(), resultIntent);

} else {

// push notification contains image

// show it with the image

showNotificationMessageWithBigImage(getApplicationContext(), title, message.getMessage(), message.getCreatedAt(), resultIntent, imageUrl);

}

}

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “Json parse error: ” + e.getMessage(), Toast.LENGTH_LONG).show();

}

 

} else {

// the push notification is silent, may be other operations needed

// like inserting it in to SQLite

}

}

 

/**

* Showing notification with text only

* */

private void showNotificationMessage(Context context, String title, String message, String timeStamp, Intent intent) {

notificationUtils = new NotificationUtils(context);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

notificationUtils.showNotificationMessage(title, message, timeStamp, intent);

}

 

/**

* Showing notification with text and image

* */

private void showNotificationMessageWithBigImage(Context context, String title, String message, String timeStamp, Intent intent, String imageUrl) {

notificationUtils = new NotificationUtils(context);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

notificationUtils.showNotificationMessage(title, message, timeStamp, intent, imageUrl);

}

}

  1. 18. Create an xml layout named xmlinside drawablefolder.
<?xml version=”1.0″ encoding=”utf-8″?>

<shape xmlns:android=”http://schemas.android.com/apk/res/android

android:shape=”rectangle”>

 

<size

android:width=”1dp”

android:height=”1dp” />

 

<solid android:color=”@color/list_divider” />

 

</shape>

  1. 19. Create an xml file named xmlinside drawablefolder. This layout acts as circular background for unread message count.
bg_circle.xml
<shape xmlns:android=”http://schemas.android.com/apk/res/android” >

 

<solid android:color=”@color/colorPrimary” />

 

<corners

android:bottomLeftRadius=”20dp”

android:bottomRightRadius=”20dp”

android:topLeftRadius=”20dp”

android:topRightRadius=”20dp” />

 

</shape>

  1. 20. Under helper package, create java. This class helps in adding a separator to recycler view items.
SimpleDividerItemDecoration.java
package info.androidhive.gcm.helper;

 

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.drawable.Drawable;

import android.support.v4.content.ContextCompat;

import android.support.v7.widget.RecyclerView;

import android.view.View;

 

import info.androidhive.gcm.R;

 

/**

* Created by Lincoln on 30/10/15.

*/

public class SimpleDividerItemDecoration extends RecyclerView.ItemDecoration {

private Drawable mDivider;

 

public SimpleDividerItemDecoration(Context context) {

mDivider = ContextCompat.getDrawable(context, R.drawable.line_divider);

}

 

@Override

public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {

int left = parent.getPaddingLeft();

int right = parent.getWidth() – parent.getPaddingRight();

 

int childCount = parent.getChildCount();

for (int i = 0; i < childCount; i++) {

View child = parent.getChildAt(i);

 

RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();

 

int top = child.getBottom() + params.bottomMargin;

int bottom = top + mDivider.getIntrinsicHeight();

 

mDivider.setBounds(left, top, right, bottom);

mDivider.draw(c);

}

}

}

  1. 21. Open xmllocated under res menuand add a logout menu item to provide logout option to user.
menu_main.xml
<menu 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

tools:context=”.activity.MainActivity”>

<item

android:id=”@+id/action_logout”

android:orderInCategory=”100″

android:title=”@string/action_logout”

app:showAsAction=”never” />

</menu>

  1. 22. Open xmland add the recycler view element.
content_main.xml
<?xml version=”1.0″ encoding=”utf-8″?>

<RelativeLayout

xmlns:android=”http://schemas.android.com/apk/res/android

xmlns:tools=”http://schemas.android.com/tools

xmlns:app=”http://schemas.android.com/apk/res-auto

android:layout_width=”match_parent”

android:layout_height=”match_parent”

app:layout_behavior=”@string/appbar_scrolling_view_behavior”

tools:showIn=”@layout/activity_main”

tools:context=”.activity.MainActivity”>

 

<android.support.v7.widget.RecyclerView

android:id=”@+id/recycler_view”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:scrollbars=”vertical” />

</RelativeLayout>

  1. 23. Finally open javaand modify the code as below. Here

> First user session is checked before setting the content view.

> On receiving the gcm registration token, user is subscribed to `global` topic. This allows us to send a notification to all the users from the admin panel.

> fetchChatRooms() is called in onCreate() method which fetches all the chat room information from the server. Once the chat rooms are received, user will be automatically subscribed to all the chat rooms topics. So that he will start receiving notifications whenever there is a active discussion going on in a chatroom.

> Broadcast receivers are registered / unregistered in onResume() / onPause() methods.

> Broadcast receivers will be triggered whenever a new push message is received in whichhandlePushNotification() method is called.

> handlePushNotification() handles the push notification by updating the recycler view items by updating last message and unread message count.

MainActivity.java
package info.androidhive.gcm.activity;

 

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.content.IntentFilter;

import android.os.Bundle;

import android.support.v4.content.LocalBroadcastManager;

import android.support.v7.app.AppCompatActivity;

import android.support.v7.widget.DefaultItemAnimator;

import android.support.v7.widget.LinearLayoutManager;

import android.support.v7.widget.RecyclerView;

import android.support.v7.widget.Toolbar;

import android.util.Log;

import android.view.Menu;

import android.view.MenuInflater;

import android.view.MenuItem;

import android.view.View;

import android.widget.Toast;

 

import com.android.volley.NetworkResponse;

import com.android.volley.Request;

import com.android.volley.Response;

import com.android.volley.VolleyError;

import com.android.volley.toolbox.StringRequest;

import com.google.android.gms.common.ConnectionResult;

import com.google.android.gms.common.GoogleApiAvailability;

 

import org.json.JSONArray;

import org.json.JSONException;

import org.json.JSONObject;

 

import java.util.ArrayList;

 

import info.androidhive.gcm.R;

import info.androidhive.gcm.adapter.ChatRoomsAdapter;

import info.androidhive.gcm.app.Config;

import info.androidhive.gcm.app.EndPoints;

import info.androidhive.gcm.app.MyApplication;

import info.androidhive.gcm.gcm.GcmIntentService;

import info.androidhive.gcm.gcm.NotificationUtils;

import info.androidhive.gcm.helper.SimpleDividerItemDecoration;

import info.androidhive.gcm.model.ChatRoom;

import info.androidhive.gcm.model.Message;

 

public class MainActivity extends AppCompatActivity {

 

private String TAG = MainActivity.class.getSimpleName();

private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

private BroadcastReceiver mRegistrationBroadcastReceiver;

private ArrayList<ChatRoom> chatRoomArrayList;

private ChatRoomsAdapter mAdapter;

private RecyclerView recyclerView;

 

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

 

/**

* Check for login session. If not logged in launch

* login activity

* */

if (MyApplication.getInstance().getPrefManager().getUser() == null) {

launchLoginActivity();

}

 

setContentView(R.layout.activity_main);

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

setSupportActionBar(toolbar);

 

recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

 

/**

* Broadcast receiver calls in two scenarios

* 1. gcm registration is completed

* 2. when new push notification is received

* */

mRegistrationBroadcastReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

 

// checking for type intent filter

if (intent.getAction().equals(Config.REGISTRATION_COMPLETE)) {

// gcm successfully registered

// now subscribe to `global` topic to receive app wide notifications

subscribeToGlobalTopic();

 

} else if (intent.getAction().equals(Config.SENT_TOKEN_TO_SERVER)) {

// gcm registration id is stored in our server’s MySQL

Log.e(TAG, “GCM registration id is sent to our server”);

 

} else if (intent.getAction().equals(Config.PUSH_NOTIFICATION)) {

// new push notification is received

handlePushNotification(intent);

}

}

};

 

chatRoomArrayList = new ArrayList<>();

mAdapter = new ChatRoomsAdapter(this, chatRoomArrayList);

LinearLayoutManager layoutManager = new LinearLayoutManager(this);

recyclerView.setLayoutManager(layoutManager);

recyclerView.addItemDecoration(new SimpleDividerItemDecoration(

getApplicationContext()

));

recyclerView.setItemAnimator(new DefaultItemAnimator());

recyclerView.setAdapter(mAdapter);

 

recyclerView.addOnItemTouchListener(new ChatRoomsAdapter.RecyclerTouchListener(getApplicationContext(), recyclerView, new ChatRoomsAdapter.ClickListener() {

@Override

public void onClick(View view, int position) {

// when chat is clicked, launch full chat thread activity

ChatRoom chatRoom = chatRoomArrayList.get(position);

Intent intent = new Intent(MainActivity.this, ChatRoomActivity.class);

intent.putExtra(“chat_room_id”, chatRoom.getId());

intent.putExtra(“name”, chatRoom.getName());

startActivity(intent);

}

 

@Override

public void onLongClick(View view, int position) {

 

}

}));

 

/**

* Always check for google play services availability before

* proceeding further with GCM

* */

if (checkPlayServices()) {

registerGCM();

fetchChatRooms();

}

}

 

/**

* Handles new push notification

*/

private void handlePushNotification(Intent intent) {

int type = intent.getIntExtra(“type”, -1);

 

// if the push is of chat room message

// simply update the UI unread messages count

if (type == Config.PUSH_TYPE_CHATROOM) {

Message message = (Message) intent.getSerializableExtra(“message”);

String chatRoomId = intent.getStringExtra(“chat_room_id”);

 

if (message != null && chatRoomId != null) {

updateRow(chatRoomId, message);

}

} else if (type == Config.PUSH_TYPE_USER) {

// push belongs to user alone

// just showing the message in a toast

Message message = (Message) intent.getSerializableExtra(“message”);

Toast.makeText(getApplicationContext(), “New push: ” + message.getMessage(), Toast.LENGTH_LONG).show();

}

 

 

}

 

/**

* Updates the chat list unread count and the last message

*/

private void updateRow(String chatRoomId, Message message) {

for (ChatRoom cr : chatRoomArrayList) {

if (cr.getId().equals(chatRoomId)) {

int index = chatRoomArrayList.indexOf(cr);

cr.setLastMessage(message.getMessage());

cr.setUnreadCount(cr.getUnreadCount() + 1);

chatRoomArrayList.remove(index);

chatRoomArrayList.add(index, cr);

break;

}

}

mAdapter.notifyDataSetChanged();

}

 

 

/**

* fetching the chat rooms by making http call

*/

private void fetchChatRooms() {

StringRequest strReq = new StringRequest(Request.Method.GET,

EndPoints.CHAT_ROOMS, new Response.Listener<String>() {

 

@Override

public void onResponse(String response) {

Log.e(TAG, “response: ” + response);

 

try {

JSONObject obj = new JSONObject(response);

 

// check for error flag

if (obj.getBoolean(“error”) == false) {

JSONArray chatRoomsArray = obj.getJSONArray(“chat_rooms”);

for (int i = 0; i < chatRoomsArray.length(); i++) {

JSONObject chatRoomsObj = (JSONObject) chatRoomsArray.get(i);

ChatRoom cr = new ChatRoom();

cr.setId(chatRoomsObj.getString(“chat_room_id”));

cr.setName(chatRoomsObj.getString(“name”));

cr.setLastMessage(“”);

cr.setUnreadCount(0);

cr.setTimestamp(chatRoomsObj.getString(“created_at”));

 

chatRoomArrayList.add(cr);

}

 

} else {

// error in fetching chat rooms

Toast.makeText(getApplicationContext(), “” + obj.getJSONObject(“error”).getString(“message”), Toast.LENGTH_LONG).show();

}

 

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “Json parse error: ” + e.getMessage(), Toast.LENGTH_LONG).show();

}

 

mAdapter.notifyDataSetChanged();

 

// subscribing to all chat room topics

subscribeToAllTopics();

}

}, new Response.ErrorListener() {

 

@Override

public void onErrorResponse(VolleyError error) {

NetworkResponse networkResponse = error.networkResponse;

Log.e(TAG, “Volley error: ” + error.getMessage() + “, code: ” + networkResponse);

Toast.makeText(getApplicationContext(), “Volley error: ” + error.getMessage(), Toast.LENGTH_SHORT).show();

}

});

 

//Adding request to request queue

MyApplication.getInstance().addToRequestQueue(strReq);

}

 

// subscribing to global topic

private void subscribeToGlobalTopic() {

Intent intent = new Intent(this, GcmIntentService.class);

intent.putExtra(GcmIntentService.KEY, GcmIntentService.SUBSCRIBE);

intent.putExtra(GcmIntentService.TOPIC, Config.TOPIC_GLOBAL);

startService(intent);

}

 

// Subscribing to all chat room topics

// each topic name starts with `topic_` followed by the ID of the chat room

// Ex: topic_1, topic_2

private void subscribeToAllTopics() {

for (ChatRoom cr : chatRoomArrayList) {

 

Intent intent = new Intent(this, GcmIntentService.class);

intent.putExtra(GcmIntentService.KEY, GcmIntentService.SUBSCRIBE);

intent.putExtra(GcmIntentService.TOPIC, “topic_” + cr.getId());

startService(intent);

}

}

 

private void launchLoginActivity() {

Intent intent = new Intent(MainActivity.this, LoginActivity.class);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

startActivity(intent);

finish();

}

 

@Override

protected void onResume() {

super.onResume();

 

// register GCM registration complete receiver

LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,

new IntentFilter(Config.REGISTRATION_COMPLETE));

 

// register new push message receiver

// by doing this, the activity will be notified each time a new message arrives

LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,

new IntentFilter(Config.PUSH_NOTIFICATION));

 

// clearing the notification tray

NotificationUtils.clearNotifications();

}

 

@Override

protected void onPause() {

LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);

super.onPause();

}

 

// starting the service to register with GCM

private void registerGCM() {

Intent intent = new Intent(this, GcmIntentService.class);

intent.putExtra(“key”, “register”);

startService(intent);

}

 

private boolean checkPlayServices() {

GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();

int resultCode = apiAvailability.isGooglePlayServicesAvailable(this);

if (resultCode != ConnectionResult.SUCCESS) {

if (apiAvailability.isUserResolvableError(resultCode)) {

apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST)

.show();

} else {

Log.i(TAG, “This device is not supported. Google Play Services not installed!”);

Toast.makeText(getApplicationContext(), “This device is not supported. Google Play Services not installed!”, Toast.LENGTH_LONG).show();

finish();

}

return false;

}

return true;

}

 

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.

MenuInflater inflater = getMenuInflater();

inflater.inflate(R.menu.menu_main, menu);

return true;

}

 

public boolean onOptionsItemSelected(MenuItem menuItem) {

switch (menuItem.getItemId()) {

case R.id.action_logout:

MyApplication.getInstance().logout();

break;

}

return super.onOptionsItemSelected(menuItem);

}

}

Now deploy the app into two devices / emulators in order to test the push notifications. You can also use the server app admin panel by visiting http://localhost/gcm_chat and type a message in a chat room.

 

Adding Chat Rooms List Screen

The chat rooms will be displayed in a list fashion for which we use RecyclerView. So add the recycler view support to build.gradle.

  1. 14. Open gradleand add the RecyclerViewdependency and rebuild the project.

compile ‘com.android.support:recyclerview-v7:23.1.1’

Below are the final dependencies of my build.gradle

build.gradle
dependencies {

compile fileTree(dir: ‘libs’, include: [‘*.jar’])

testCompile ‘junit:junit:4.12’

compile ‘com.android.support:appcompat-v7:23.1.1’

compile ‘com.android.support:design:23.1.1’

compile ‘com.mcxiaoke.volley:library-aar:1.0.0’

compile ‘com.android.support:recyclerview-v7:23.1.1’

compile “com.google.android.gms:play-services:8.3.0”

}

  1. 15. Create a layout named xml. This layout renders a single chat room information like chat room namelast messageunread message countand the timestampin the recycler view.
chat_rooms_list_row
<?xml version=”1.0″ encoding=”utf-8″?>

<RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android

android:layout_width=”match_parent”

android:layout_height=”match_parent”

android:orientation=”vertical”

android:paddingBottom=”16dp”

android:paddingLeft=”16dp”

android:paddingRight=”16dp”

android:paddingTop=”16dp”>

 

<TextView

android:id=”@+id/name”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:layout_alignParentTop=”true”

android:textColor=”#444444″

android:textStyle=”bold”

android:textSize=”16dp” />

 

<TextView android:id=”@+id/message”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:layout_below=”@id/name”

android:textColor=”#888888″

android:layout_marginTop=”5dp”

android:text=”Seems gcm will take some time”/>

 

<TextView android:id=”@+id/timestamp”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:text=”12:00 AM”

android:textSize=”10dp”

android:layout_alignParentRight=”true”

android:layout_alignParentTop=”true”/>

 

<TextView android:id=”@+id/count”

android:layout_width=”20dp”

android:layout_height=”20dp”

android:gravity=”center”

android:textSize=”10dp”

android:textColor=”@android:color/white”

android:layout_below=”@id/timestamp”

android:layout_marginTop=”5dp”

android:layout_alignParentRight=”true”

android:text=”5″

android:background=”@drawable/bg_circle”/>

 

</RelativeLayout>

  1. 16. Create a new package named adapter. Under adapter create a class named java. This is a recycler view adapter which provides data to chat rooms list view.
ChatRoomsAdapter.java
package info.androidhive.gcm.adapter;

 

import android.content.Context;

import android.support.v7.widget.RecyclerView;

import android.view.GestureDetector;

import android.view.LayoutInflater;

import android.view.MotionEvent;

import android.view.View;

import android.view.ViewGroup;

import android.widget.TextView;

 

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Calendar;

import java.util.Date;

 

import info.androidhive.gcm.R;

import info.androidhive.gcm.model.ChatRoom;

 

 

public class ChatRoomsAdapter extends RecyclerView.Adapter<ChatRoomsAdapter.ViewHolder> {

 

private Context mContext;

private ArrayList<ChatRoom> chatRoomArrayList;

private static String today;

 

public class ViewHolder extends RecyclerView.ViewHolder {

public TextView name, message, timestamp, count;

 

public ViewHolder(View view) {

super(view);

name = (TextView) view.findViewById(R.id.name);

message = (TextView) view.findViewById(R.id.message);

timestamp = (TextView) view.findViewById(R.id.timestamp);

count = (TextView) view.findViewById(R.id.count);

}

}

 

 

public ChatRoomsAdapter(Context mContext, ArrayList<ChatRoom> chatRoomArrayList) {

this.mContext = mContext;

this.chatRoomArrayList = chatRoomArrayList;

 

Calendar calendar = Calendar.getInstance();

today = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));

}

 

@Override

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

View itemView = LayoutInflater.from(parent.getContext())

.inflate(R.layout.chat_rooms_list_row, parent, false);

 

return new ViewHolder(itemView);

}

 

@Override

public void onBindViewHolder(ViewHolder holder, int position) {

ChatRoom chatRoom = chatRoomArrayList.get(position);

holder.name.setText(chatRoom.getName());

holder.message.setText(chatRoom.getLastMessage());

if (chatRoom.getUnreadCount() > 0) {

holder.count.setText(String.valueOf(chatRoom.getUnreadCount()));

holder.count.setVisibility(View.VISIBLE);

} else {

holder.count.setVisibility(View.GONE);

}

 

holder.timestamp.setText(getTimeStamp(chatRoom.getTimestamp()));

}

 

@Override

public int getItemCount() {

return chatRoomArrayList.size();

}

 

public static String getTimeStamp(String dateStr) {

SimpleDateFormat format = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

String timestamp = “”;

 

today = today.length() < 2 ? “0” + today : today;

 

try {

Date date = format.parse(dateStr);

SimpleDateFormat todayFormat = new SimpleDateFormat(“dd”);

String dateToday = todayFormat.format(date);

format = dateToday.equals(today) ? new SimpleDateFormat(“hh:mm a”) : new SimpleDateFormat(“dd LLL, hh:mm a”);

String date1 = format.format(date);

timestamp = date1.toString();

} catch (ParseException e) {

e.printStackTrace();

}

 

return timestamp;

}

 

public interface ClickListener {

void onClick(View view, int position);

 

void onLongClick(View view, int position);

}

 

public static class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {

 

private GestureDetector gestureDetector;

private ChatRoomsAdapter.ClickListener clickListener;

 

public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ChatRoomsAdapter.ClickListener clickListener) {

this.clickListener = clickListener;

gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {

@Override

public boolean onSingleTapUp(MotionEvent e) {

return true;

}

 

@Override

public void onLongPress(MotionEvent e) {

View child = recyclerView.findChildViewUnder(e.getX(), e.getY());

if (child != null && clickListener != null) {

clickListener.onLongClick(child, recyclerView.getChildPosition(child));

}

}

});

}

 

@Override

public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

 

View child = rv.findChildViewUnder(e.getX(), e.getY());

if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {

clickListener.onClick(child, rv.getChildPosition(child));

}

return false;

}

 

@Override

public void onTouchEvent(RecyclerView rv, MotionEvent e) {

}

 

@Override

public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

 

}

}

}

  1. 17. Open javaand modify the code as below. Here I have added few methods to handle push notification message. First the push notification will be identified by flagnode and then appropriate action will be taken.

> processChatRoomPush() method will be called whenever chat room push message is received

> processUserMessage() will be called when the push is specific to logged in user.

MyGcmPushReceiver.java
package info.androidhive.gcm.gcm;

 

import android.content.Context;

import android.content.Intent;

import android.os.Bundle;

import android.support.v4.content.LocalBroadcastManager;

import android.text.TextUtils;

import android.util.Log;

import android.widget.Toast;

 

import com.google.android.gms.gcm.GcmListenerService;

 

import org.json.JSONException;

import org.json.JSONObject;

 

import info.androidhive.gcm.activity.ChatRoomActivity;

import info.androidhive.gcm.activity.MainActivity;

import info.androidhive.gcm.app.Config;

import info.androidhive.gcm.app.MyApplication;

import info.androidhive.gcm.model.Message;

import info.androidhive.gcm.model.User;

 

public class MyGcmPushReceiver extends GcmListenerService {

 

private static final String TAG = MyGcmPushReceiver.class.getSimpleName();

 

private NotificationUtils notificationUtils;

 

/**

* Called when message is received.

*

* @param from   SenderID of the sender.

* @param bundle Data bundle containing message data as key/value pairs.

*               For Set of keys use data.keySet().

*/

 

@Override

public void onMessageReceived(String from, Bundle bundle) {

String title = bundle.getString(“title”);

Boolean isBackground = Boolean.valueOf(bundle.getString(“is_background”));

String flag = bundle.getString(“flag”);

String data = bundle.getString(“data”);

Log.d(TAG, “From: ” + from);

Log.d(TAG, “title: ” + title);

Log.d(TAG, “isBackground: ” + isBackground);

Log.d(TAG, “flag: ” + flag);

Log.d(TAG, “data: ” + data);

 

if (flag == null)

return;

 

if(MyApplication.getInstance().getPrefManager().getUser() == null){

// user is not logged in, skipping push notification

Log.e(TAG, “user is not logged in, skipping push notification”);

return;

}

 

if (from.startsWith(“/topics/”)) {

// message received from some topic.

} else {

// normal downstream message.

}

 

switch (Integer.parseInt(flag)) {

case Config.PUSH_TYPE_CHATROOM:

// push notification belongs to a chat room

processChatRoomPush(title, isBackground, data);

break;

case Config.PUSH_TYPE_USER:

// push notification is specific to user

processUserMessage(title, isBackground, data);

break;

}

}

 

/**

* Processing chat room push message

* this message will be broadcasts to all the activities registered

* */

private void processChatRoomPush(String title, boolean isBackground, String data) {

if (!isBackground) {

 

try {

JSONObject datObj = new JSONObject(data);

 

String chatRoomId = datObj.getString(“chat_room_id”);

 

JSONObject mObj = datObj.getJSONObject(“message”);

Message message = new Message();

message.setMessage(mObj.getString(“message”));

message.setId(mObj.getString(“message_id”));

message.setCreatedAt(mObj.getString(“created_at”));

 

JSONObject uObj = datObj.getJSONObject(“user”);

 

// skip the message if the message belongs to same user as

// the user would be having the same message when he was sending

// but it might differs in your scenario

if (uObj.getString(“user_id”).equals(MyApplication.getInstance().getPrefManager().getUser().getId())) {

Log.e(TAG, “Skipping the push message as it belongs to same user”);

return;

}

 

User user = new User();

user.setId(uObj.getString(“user_id”));

user.setEmail(uObj.getString(“email”));

user.setName(uObj.getString(“name”));

message.setUser(user);

 

// verifying whether the app is in background or foreground

if (!NotificationUtils.isAppIsInBackground(getApplicationContext())) {

 

// app is in foreground, broadcast the push message

Intent pushNotification = new Intent(Config.PUSH_NOTIFICATION);

pushNotification.putExtra(“type”, Config.PUSH_TYPE_CHATROOM);

pushNotification.putExtra(“message”, message);

pushNotification.putExtra(“chat_room_id”, chatRoomId);

LocalBroadcastManager.getInstance(this).sendBroadcast(pushNotification);

 

// play notification sound

NotificationUtils notificationUtils = new NotificationUtils();

notificationUtils.playNotificationSound();

} else {

 

// app is in background. show the message in notification try

Intent resultIntent = new Intent(getApplicationContext(), ChatRoomActivity.class);

resultIntent.putExtra(“chat_room_id”, chatRoomId);

showNotificationMessage(getApplicationContext(), title, user.getName() + ” : ” + message.getMessage(), message.getCreatedAt(), resultIntent);

}

 

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “Json parse error: ” + e.getMessage(), Toast.LENGTH_LONG).show();

}

 

} else {

// the push notification is silent, may be other operations needed

// like inserting it in to SQLite

}

}

 

/**

* Processing user specific push message

* It will be displayed with / without image in push notification tray

* */

private void processUserMessage(String title, boolean isBackground, String data) {

if (!isBackground) {

 

try {

JSONObject datObj = new JSONObject(data);

 

String imageUrl = datObj.getString(“image”);

 

JSONObject mObj = datObj.getJSONObject(“message”);

Message message = new Message();

message.setMessage(mObj.getString(“message”));

message.setId(mObj.getString(“message_id”));

message.setCreatedAt(mObj.getString(“created_at”));

 

JSONObject uObj = datObj.getJSONObject(“user”);

User user = new User();

user.setId(uObj.getString(“user_id”));

user.setEmail(uObj.getString(“email”));

user.setName(uObj.getString(“name”));

message.setUser(user);

 

// verifying whether the app is in background or foreground

if (!NotificationUtils.isAppIsInBackground(getApplicationContext())) {

 

// app is in foreground, broadcast the push message

Intent pushNotification = new Intent(Config.PUSH_NOTIFICATION);

pushNotification.putExtra(“type”, Config.PUSH_TYPE_USER);

pushNotification.putExtra(“message”, message);

LocalBroadcastManager.getInstance(this).sendBroadcast(pushNotification);

 

// play notification sound

NotificationUtils notificationUtils = new NotificationUtils();

notificationUtils.playNotificationSound();

} else {

 

// app is in background. show the message in notification try

Intent resultIntent = new Intent(getApplicationContext(), MainActivity.class);

 

// check for push notification image attachment

if (TextUtils.isEmpty(imageUrl)) {

showNotificationMessage(getApplicationContext(), title, user.getName() + ” : ” + message.getMessage(), message.getCreatedAt(), resultIntent);

} else {

// push notification contains image

// show it with the image

showNotificationMessageWithBigImage(getApplicationContext(), title, message.getMessage(), message.getCreatedAt(), resultIntent, imageUrl);

}

}

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “Json parse error: ” + e.getMessage(), Toast.LENGTH_LONG).show();

}

 

} else {

// the push notification is silent, may be other operations needed

// like inserting it in to SQLite

}

}

 

/**

* Showing notification with text only

* */

private void showNotificationMessage(Context context, String title, String message, String timeStamp, Intent intent) {

notificationUtils = new NotificationUtils(context);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

notificationUtils.showNotificationMessage(title, message, timeStamp, intent);

}

 

/**

* Showing notification with text and image

* */

private void showNotificationMessageWithBigImage(Context context, String title, String message, String timeStamp, Intent intent, String imageUrl) {

notificationUtils = new NotificationUtils(context);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

notificationUtils.showNotificationMessage(title, message, timeStamp, intent, imageUrl);

}

}

  1. 18. Create an xml layout named xmlinside drawablefolder.
<?xml version=”1.0″ encoding=”utf-8″?>

<shape xmlns:android=”http://schemas.android.com/apk/res/android

android:shape=”rectangle”>

 

<size

android:width=”1dp”

android:height=”1dp” />

 

<solid android:color=”@color/list_divider” />

 

</shape>

  1. 19. Create an xml file named xmlinside drawablefolder. This layout acts as circular background for unread message count.
bg_circle.xml
<shape xmlns:android=”http://schemas.android.com/apk/res/android” >

 

<solid android:color=”@color/colorPrimary” />

 

<corners

android:bottomLeftRadius=”20dp”

android:bottomRightRadius=”20dp”

android:topLeftRadius=”20dp”

android:topRightRadius=”20dp” />

 

</shape>

  1. 20. Under helper package, create java. This class helps in adding a separator to recycler view items.
SimpleDividerItemDecoration.java
package info.androidhive.gcm.helper;

 

import android.content.Context;

import android.graphics.Canvas;

import android.graphics.drawable.Drawable;

import android.support.v4.content.ContextCompat;

import android.support.v7.widget.RecyclerView;

import android.view.View;

 

import info.androidhive.gcm.R;

 

/**

* Created by Lincoln on 30/10/15.

*/

public class SimpleDividerItemDecoration extends RecyclerView.ItemDecoration {

private Drawable mDivider;

 

public SimpleDividerItemDecoration(Context context) {

mDivider = ContextCompat.getDrawable(context, R.drawable.line_divider);

}

 

@Override

public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {

int left = parent.getPaddingLeft();

int right = parent.getWidth() – parent.getPaddingRight();

 

int childCount = parent.getChildCount();

for (int i = 0; i < childCount; i++) {

View child = parent.getChildAt(i);

 

RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();

 

int top = child.getBottom() + params.bottomMargin;

int bottom = top + mDivider.getIntrinsicHeight();

 

mDivider.setBounds(left, top, right, bottom);

mDivider.draw(c);

}

}

}

  1. 21. Open xmllocated under res menuand add a logout menu item to provide logout option to user.
menu_main.xml
<menu 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

tools:context=”.activity.MainActivity”>

<item

android:id=”@+id/action_logout”

android:orderInCategory=”100″

android:title=”@string/action_logout”

app:showAsAction=”never” />

</menu>

  1. 22. Open xmland add the recycler view element.
content_main.xml
<?xml version=”1.0″ encoding=”utf-8″?>

<RelativeLayout

xmlns:android=”http://schemas.android.com/apk/res/android

xmlns:tools=”http://schemas.android.com/tools

xmlns:app=”http://schemas.android.com/apk/res-auto

android:layout_width=”match_parent”

android:layout_height=”match_parent”

app:layout_behavior=”@string/appbar_scrolling_view_behavior”

tools:showIn=”@layout/activity_main”

tools:context=”.activity.MainActivity”>

 

<android.support.v7.widget.RecyclerView

android:id=”@+id/recycler_view”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:scrollbars=”vertical” />

</RelativeLayout>

  1. 23. Finally open javaand modify the code as below. Here

> First user session is checked before setting the content view.

> On receiving the gcm registration token, user is subscribed to `global` topic. This allows us to send a notification to all the users from the admin panel.

> fetchChatRooms() is called in onCreate() method which fetches all the chat room information from the server. Once the chat rooms are received, user will be automatically subscribed to all the chat rooms topics. So that he will start receiving notifications whenever there is a active discussion going on in a chatroom.

> Broadcast receivers are registered / unregistered in onResume() / onPause() methods.

> Broadcast receivers will be triggered whenever a new push message is received in whichhandlePushNotification() method is called.

> handlePushNotification() handles the push notification by updating the recycler view items by updating last message and unread message count.

MainActivity.java
package info.androidhive.gcm.activity;

 

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.content.IntentFilter;

import android.os.Bundle;

import android.support.v4.content.LocalBroadcastManager;

import android.support.v7.app.AppCompatActivity;

import android.support.v7.widget.DefaultItemAnimator;

import android.support.v7.widget.LinearLayoutManager;

import android.support.v7.widget.RecyclerView;

import android.support.v7.widget.Toolbar;

import android.util.Log;

import android.view.Menu;

import android.view.MenuInflater;

import android.view.MenuItem;

import android.view.View;

import android.widget.Toast;

 

import com.android.volley.NetworkResponse;

import com.android.volley.Request;

import com.android.volley.Response;

import com.android.volley.VolleyError;

import com.android.volley.toolbox.StringRequest;

import com.google.android.gms.common.ConnectionResult;

import com.google.android.gms.common.GoogleApiAvailability;

 

import org.json.JSONArray;

import org.json.JSONException;

import org.json.JSONObject;

 

import java.util.ArrayList;

 

import info.androidhive.gcm.R;

import info.androidhive.gcm.adapter.ChatRoomsAdapter;

import info.androidhive.gcm.app.Config;

import info.androidhive.gcm.app.EndPoints;

import info.androidhive.gcm.app.MyApplication;

import info.androidhive.gcm.gcm.GcmIntentService;

import info.androidhive.gcm.gcm.NotificationUtils;

import info.androidhive.gcm.helper.SimpleDividerItemDecoration;

import info.androidhive.gcm.model.ChatRoom;

import info.androidhive.gcm.model.Message;

 

public class MainActivity extends AppCompatActivity {

 

private String TAG = MainActivity.class.getSimpleName();

private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

private BroadcastReceiver mRegistrationBroadcastReceiver;

private ArrayList<ChatRoom> chatRoomArrayList;

private ChatRoomsAdapter mAdapter;

private RecyclerView recyclerView;

 

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

 

/**

* Check for login session. If not logged in launch

* login activity

* */

if (MyApplication.getInstance().getPrefManager().getUser() == null) {

launchLoginActivity();

}

 

setContentView(R.layout.activity_main);

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

setSupportActionBar(toolbar);

 

recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

 

/**

* Broadcast receiver calls in two scenarios

* 1. gcm registration is completed

* 2. when new push notification is received

* */

mRegistrationBroadcastReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

 

// checking for type intent filter

if (intent.getAction().equals(Config.REGISTRATION_COMPLETE)) {

// gcm successfully registered

// now subscribe to `global` topic to receive app wide notifications

subscribeToGlobalTopic();

 

} else if (intent.getAction().equals(Config.SENT_TOKEN_TO_SERVER)) {

// gcm registration id is stored in our server’s MySQL

Log.e(TAG, “GCM registration id is sent to our server”);

 

} else if (intent.getAction().equals(Config.PUSH_NOTIFICATION)) {

// new push notification is received

handlePushNotification(intent);

}

}

};

 

chatRoomArrayList = new ArrayList<>();

mAdapter = new ChatRoomsAdapter(this, chatRoomArrayList);

LinearLayoutManager layoutManager = new LinearLayoutManager(this);

recyclerView.setLayoutManager(layoutManager);

recyclerView.addItemDecoration(new SimpleDividerItemDecoration(

getApplicationContext()

));

recyclerView.setItemAnimator(new DefaultItemAnimator());

recyclerView.setAdapter(mAdapter);

 

recyclerView.addOnItemTouchListener(new ChatRoomsAdapter.RecyclerTouchListener(getApplicationContext(), recyclerView, new ChatRoomsAdapter.ClickListener() {

@Override

public void onClick(View view, int position) {

// when chat is clicked, launch full chat thread activity

ChatRoom chatRoom = chatRoomArrayList.get(position);

Intent intent = new Intent(MainActivity.this, ChatRoomActivity.class);

intent.putExtra(“chat_room_id”, chatRoom.getId());

intent.putExtra(“name”, chatRoom.getName());

startActivity(intent);

}

 

@Override

public void onLongClick(View view, int position) {

 

}

}));

 

/**

* Always check for google play services availability before

* proceeding further with GCM

* */

if (checkPlayServices()) {

registerGCM();

fetchChatRooms();

}

}

 

/**

* Handles new push notification

*/

private void handlePushNotification(Intent intent) {

int type = intent.getIntExtra(“type”, -1);

 

// if the push is of chat room message

// simply update the UI unread messages count

if (type == Config.PUSH_TYPE_CHATROOM) {

Message message = (Message) intent.getSerializableExtra(“message”);

String chatRoomId = intent.getStringExtra(“chat_room_id”);

 

if (message != null && chatRoomId != null) {

updateRow(chatRoomId, message);

}

} else if (type == Config.PUSH_TYPE_USER) {

// push belongs to user alone

// just showing the message in a toast

Message message = (Message) intent.getSerializableExtra(“message”);

Toast.makeText(getApplicationContext(), “New push: ” + message.getMessage(), Toast.LENGTH_LONG).show();

}

 

 

}

 

/**

* Updates the chat list unread count and the last message

*/

private void updateRow(String chatRoomId, Message message) {

for (ChatRoom cr : chatRoomArrayList) {

if (cr.getId().equals(chatRoomId)) {

int index = chatRoomArrayList.indexOf(cr);

cr.setLastMessage(message.getMessage());

cr.setUnreadCount(cr.getUnreadCount() + 1);

chatRoomArrayList.remove(index);

chatRoomArrayList.add(index, cr);

break;

}

}

mAdapter.notifyDataSetChanged();

}

 

 

/**

* fetching the chat rooms by making http call

*/

private void fetchChatRooms() {

StringRequest strReq = new StringRequest(Request.Method.GET,

EndPoints.CHAT_ROOMS, new Response.Listener<String>() {

 

@Override

public void onResponse(String response) {

Log.e(TAG, “response: ” + response);

 

try {

JSONObject obj = new JSONObject(response);

 

// check for error flag

if (obj.getBoolean(“error”) == false) {

JSONArray chatRoomsArray = obj.getJSONArray(“chat_rooms”);

for (int i = 0; i < chatRoomsArray.length(); i++) {

JSONObject chatRoomsObj = (JSONObject) chatRoomsArray.get(i);

ChatRoom cr = new ChatRoom();

cr.setId(chatRoomsObj.getString(“chat_room_id”));

cr.setName(chatRoomsObj.getString(“name”));

cr.setLastMessage(“”);

cr.setUnreadCount(0);

cr.setTimestamp(chatRoomsObj.getString(“created_at”));

 

chatRoomArrayList.add(cr);

}

 

} else {

// error in fetching chat rooms

Toast.makeText(getApplicationContext(), “” + obj.getJSONObject(“error”).getString(“message”), Toast.LENGTH_LONG).show();

}

 

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “Json parse error: ” + e.getMessage(), Toast.LENGTH_LONG).show();

}

 

mAdapter.notifyDataSetChanged();

 

// subscribing to all chat room topics

subscribeToAllTopics();

}

}, new Response.ErrorListener() {

 

@Override

public void onErrorResponse(VolleyError error) {

NetworkResponse networkResponse = error.networkResponse;

Log.e(TAG, “Volley error: ” + error.getMessage() + “, code: ” + networkResponse);

Toast.makeText(getApplicationContext(), “Volley error: ” + error.getMessage(), Toast.LENGTH_SHORT).show();

}

});

 

//Adding request to request queue

MyApplication.getInstance().addToRequestQueue(strReq);

}

 

// subscribing to global topic

private void subscribeToGlobalTopic() {

Intent intent = new Intent(this, GcmIntentService.class);

intent.putExtra(GcmIntentService.KEY, GcmIntentService.SUBSCRIBE);

intent.putExtra(GcmIntentService.TOPIC, Config.TOPIC_GLOBAL);

startService(intent);

}

 

// Subscribing to all chat room topics

// each topic name starts with `topic_` followed by the ID of the chat room

// Ex: topic_1, topic_2

private void subscribeToAllTopics() {

for (ChatRoom cr : chatRoomArrayList) {

 

Intent intent = new Intent(this, GcmIntentService.class);

intent.putExtra(GcmIntentService.KEY, GcmIntentService.SUBSCRIBE);

intent.putExtra(GcmIntentService.TOPIC, “topic_” + cr.getId());

startService(intent);

}

}

 

private void launchLoginActivity() {

Intent intent = new Intent(MainActivity.this, LoginActivity.class);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

startActivity(intent);

finish();

}

 

@Override

protected void onResume() {

super.onResume();

 

// register GCM registration complete receiver

LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,

new IntentFilter(Config.REGISTRATION_COMPLETE));

 

// register new push message receiver

// by doing this, the activity will be notified each time a new message arrives

LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,

new IntentFilter(Config.PUSH_NOTIFICATION));

 

// clearing the notification tray

NotificationUtils.clearNotifications();

}

 

@Override

protected void onPause() {

LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);

super.onPause();

}

 

// starting the service to register with GCM

private void registerGCM() {

Intent intent = new Intent(this, GcmIntentService.class);

intent.putExtra(“key”, “register”);

startService(intent);

}

 

private boolean checkPlayServices() {

GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();

int resultCode = apiAvailability.isGooglePlayServicesAvailable(this);

if (resultCode != ConnectionResult.SUCCESS) {

if (apiAvailability.isUserResolvableError(resultCode)) {

apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST)

.show();

} else {

Log.i(TAG, “This device is not supported. Google Play Services not installed!”);

Toast.makeText(getApplicationContext(), “This device is not supported. Google Play Services not installed!”, Toast.LENGTH_LONG).show();

finish();

}

return false;

}

return true;

}

 

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.

MenuInflater inflater = getMenuInflater();

inflater.inflate(R.menu.menu_main, menu);

return true;

}

 

public boolean onOptionsItemSelected(MenuItem menuItem) {

switch (menuItem.getItemId()) {

case R.id.action_logout:

MyApplication.getInstance().logout();

break;

}

return super.onOptionsItemSelected(menuItem);

}

}

Now deploy the app into two devices / emulators in order to test the push notifications. You can also use the server app admin panel by visiting http://localhost/gcm_chat and type a message in a chat room.

android-simple-realtime-chat-app-using-gcm-android-php-mysql

Adding Single Chat Room Thread

The single chat room thread screen contains the messages from different users aligned left or right on the screen. All the messages belongs to other users will be aligned to left. The messages belongs to current logged in user will be aligned to right.

This alignment can be achieved using a recycler view and two different xml layouts for self and other messages.

  1. 24. Create an xml file named xmlinside drawablefolder. This layout adds a rounded corner background to other’s messages.
bg_bubble_white.xml
<?xml version=”1.0″ encoding=”utf-8″?>

<shape

xmlns:android=”http://schemas.android.com/apk/res/android

android:shape=”rectangle”>

 

<!– view background color –>

<solid

android:color=”@android:color/white” >

</solid>

 

 

 

<!– If you want to add some padding –>

<padding

android:left=”10dp”

android:top=”4dp”

android:right=”10dp”

android:bottom=”4dp”    >

</padding>

 

<!– Here is the corner radius –>

<corners

android:radius=”5dp”   >

</corners>

 

</shape>

  1. 25. Create an xml file named xmlinside drawablefolder. This layout adds a rounded corner background to self messages.
bg_bubble_gray.xml
<?xml version=”1.0″ encoding=”utf-8″?>

<shape

xmlns:android=”http://schemas.android.com/apk/res/android

android:shape=”rectangle”>

 

<!– view background color –>

<solid

android:color=”@color/bg_bubble_self” >

</solid>

 

 

 

<!– If you want to add some padding –>

<padding

android:left=”10dp”

android:top=”4dp”

android:right=”10dp”

android:bottom=”4dp”    >

</padding>

 

<!– Here is the corner radius –>

<corners

android:radius=”5dp”   >

</corners>

 

</shape>

  1. 26. Create two xml layouts named xmland chat_item_other.xmlto render both self and other messages.
chat_item_self.xml
<RelativeLayout 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:gravity=”right”

android:orientation=”horizontal”

android:paddingBottom=”5dp”

android:paddingLeft=”16dp”

android:paddingRight=”16dp”>

 

<TextView

android:id=”@+id/message”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:layout_alignParentRight=”true”

android:layout_marginRight=”10dp”

android:textIsSelectable=”true”

android:background=”@drawable/bg_bubble_gray”

android:textSize=”14dp” />

 

<TextView

android:id=”@+id/timestamp”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:layout_alignRight=”@id/message”

android:layout_below=”@id/message”

android:layout_marginBottom=”25dp”

android:padding=”10dp”

android:textSize=”10dp” />

 

</RelativeLayout>

chat_item_other.xml
<RelativeLayout 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=”horizontal”

android:paddingTop=”5dp”

android:paddingLeft=”16dp”

android:paddingRight=”16dp”>

 

<TextView

android:id=”@+id/message”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:layout_alignParentLeft=”true”

android:layout_marginLeft=”10dp”

android:textIsSelectable=”true”

android:background=”@drawable/bg_bubble_white”

android:textSize=”14dp” />

 

<TextView

android:id=”@+id/timestamp”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:layout_alignParentLeft=”true”

android:layout_below=”@id/message”

android:layout_marginBottom=”25dp”

android:layout_marginLeft=”10dp”

android:paddingLeft=”10dp”

android:paddingTop=”6dp”

android:textSize=”10dp” />

</RelativeLayout>

  1. 27. Create a class named javaunder adapterpackage.
ChatRoomThreadAdapter.java
This adapter class identifies the current logged in user messages by user id and align the messages left or right by inflating two different xml layouts.

package info.androidhive.gcm.adapter;

 

import android.content.Context;

import android.support.v7.widget.RecyclerView;

import android.view.LayoutInflater;

import android.view.View;

import android.view.ViewGroup;

import android.widget.TextView;

 

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.ArrayList;

import java.util.Calendar;

import java.util.Date;

 

import info.androidhive.gcm.R;

import info.androidhive.gcm.model.Message;

 

public class ChatRoomThreadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

 

private static String TAG = ChatRoomThreadAdapter.class.getSimpleName();

 

private String userId;

private int SELF = 100;

private static String today;

 

private Context mContext;

private ArrayList<Message> messageArrayList;

 

public class ViewHolder extends RecyclerView.ViewHolder {

TextView message, timestamp;

 

public ViewHolder(View view) {

super(view);

message = (TextView) itemView.findViewById(R.id.message);

timestamp = (TextView) itemView.findViewById(R.id.timestamp);

}

}

 

 

public ChatRoomThreadAdapter(Context mContext, ArrayList<Message> messageArrayList, String userId) {

this.mContext = mContext;

this.messageArrayList = messageArrayList;

this.userId = userId;

 

Calendar calendar = Calendar.getInstance();

today = String.valueOf(calendar.get(Calendar.DAY_OF_MONTH));

}

 

@Override

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

View itemView;

 

// view type is to identify where to render the chat message

// left or right

if (viewType == SELF) {

// self message

itemView = LayoutInflater.from(parent.getContext())

.inflate(R.layout.chat_item_self, parent, false);

} else {

// others message

itemView = LayoutInflater.from(parent.getContext())

.inflate(R.layout.chat_item_other, parent, false);

}

 

 

return new ViewHolder(itemView);

}

 

 

@Override

public int getItemViewType(int position) {

Message message = messageArrayList.get(position);

if (message.getUser().getId().equals(userId)) {

return SELF;

}

 

return position;

}

 

@Override

public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {

Message message = messageArrayList.get(position);

((ViewHolder) holder).message.setText(message.getMessage());

 

String timestamp = getTimeStamp(message.getCreatedAt());

 

if (message.getUser().getName() != null)

timestamp = message.getUser().getName() + “, ” + timestamp;

 

((ViewHolder) holder).timestamp.setText(timestamp);

}

 

@Override

public int getItemCount() {

return messageArrayList.size();

}

 

public static String getTimeStamp(String dateStr) {

SimpleDateFormat format = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);

String timestamp = “”;

 

today = today.length() < 2 ? “0” + today : today;

 

try {

Date date = format.parse(dateStr);

SimpleDateFormat todayFormat = new SimpleDateFormat(“dd”);

String dateToday = todayFormat.format(date);

format = dateToday.equals(today) ? new SimpleDateFormat(“hh:mm a”) : new SimpleDateFormat(“dd LLL, hh:mm a”);

String date1 = format.format(date);

timestamp = date1.toString();

} catch (ParseException e) {

e.printStackTrace();

}

return timestamp;

}

}

  1. 28. Create a new activity class named javaby right clicking on activity New Activity Blank Activity. This creates two layout files named activity_chat_room.xmland content_chat_room.xml.
activity_chat_room.xml
<?xml version=”1.0″ encoding=”utf-8″?>

<android.support.design.widget.CoordinatorLayout 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”

android:fitsSystemWindows=”true”

tools:context=”.activity.ChatRoomActivity”>

 

<android.support.design.widget.AppBarLayout

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:theme=”@style/AppTheme.AppBarOverlay”>

 

<android.support.v7.widget.Toolbar

android:id=”@+id/toolbar”

android:layout_width=”match_parent”

android:layout_height=”?attr/actionBarSize”

android:background=”?attr/colorPrimary”

app:popupTheme=”@style/AppTheme.PopupOverlay” />

 

</android.support.design.widget.AppBarLayout>

 

<include layout=”@layout/content_chat_room” />

 

</android.support.design.widget.CoordinatorLayout>

  1. 29. Open xmland add the below code. Here I have added a recycler view and an EditText element to enter the new message in a chat room.
content_chat_room.xml
<?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”

app:layout_behavior=”@string/appbar_scrolling_view_behavior”

tools:context=”.activity.ChatRoomActivity”

tools:showIn=”@layout/activity_chat_room”>

 

<android.support.v7.widget.RecyclerView

android:id=”@+id/recycler_view”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:paddingBottom=”20dp”

android:scrollbars=”vertical” />

 

<LinearLayout

android:background=”@android:color/white”

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:layout_alignParentBottom=”true”

android:orientation=”horizontal”

android:weightSum=”4″>

 

<EditText android:id=”@+id/message”

android:layout_width=”0dp”

android:hint=”Enter message”

android:paddingLeft=”10dp”

android:background=”@null”

android:layout_marginRight=”10dp”

android:layout_marginLeft=”16dp”

android:lines=”1″

android:layout_height=”wrap_content”

android:layout_weight=”3″ />

 

<Button android:id=”@+id/btn_send”

android:layout_width=”0dp”

android:layout_height=”wrap_content”

android:layout_weight=”1″

android:background=”@null”

android:text=”SEND”

android:textSize=”16dp”

android:textColor=”@color/colorPrimary” />

 

</LinearLayout>

 

</RelativeLayout>

  1. 30. Open javamodify the code as below.

> fetchChatThread() method fetches all the previous messages exchanged in the chat room.

> Broadcast receiver is registered for PUSH_NOTIFICATION in onResume() method.

> handlePushNotification() method handles the push message and append it to adapter array list. Upon calling the notifyDataSetChanged() the new message will be displayed in discussion thread.

> The self and other messages are aligned left or right by comparing the user id in the push message with the id of the user currently logged in.

> sendMessage() method sends a new message to server to post it in the chat room. On the server the message will sent to gcm server to broadcast it to other devices subscribed to that chat room’s topic.

ChatRoomActivity.java
package info.androidhive.gcm.activity;

 

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.content.IntentFilter;

import android.os.Bundle;

import android.support.v4.content.LocalBroadcastManager;

import android.support.v7.app.AppCompatActivity;

import android.support.v7.widget.DefaultItemAnimator;

import android.support.v7.widget.LinearLayoutManager;

import android.support.v7.widget.RecyclerView;

import android.support.v7.widget.Toolbar;

import android.text.TextUtils;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.Toast;

 

import com.android.volley.DefaultRetryPolicy;

import com.android.volley.NetworkResponse;

import com.android.volley.Request;

import com.android.volley.Response;

import com.android.volley.RetryPolicy;

import com.android.volley.VolleyError;

import com.android.volley.toolbox.StringRequest;

 

import org.json.JSONArray;

import org.json.JSONException;

import org.json.JSONObject;

 

import java.util.ArrayList;

import java.util.HashMap;

import java.util.Map;

 

import info.androidhive.gcm.R;

import info.androidhive.gcm.adapter.ChatRoomThreadAdapter;

import info.androidhive.gcm.app.Config;

import info.androidhive.gcm.app.EndPoints;

import info.androidhive.gcm.app.MyApplication;

import info.androidhive.gcm.gcm.NotificationUtils;

import info.androidhive.gcm.model.Message;

import info.androidhive.gcm.model.User;

 

public class ChatRoomActivity extends AppCompatActivity {

 

private String TAG = ChatRoomActivity.class.getSimpleName();

 

private String chatRoomId;

private RecyclerView recyclerView;

private ChatRoomThreadAdapter mAdapter;

private ArrayList<Message> messageArrayList;

private BroadcastReceiver mRegistrationBroadcastReceiver;

private EditText inputMessage;

private Button btnSend;

 

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_chat_room);

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

setSupportActionBar(toolbar);

 

inputMessage = (EditText) findViewById(R.id.message);

btnSend = (Button) findViewById(R.id.btn_send);

 

Intent intent = getIntent();

chatRoomId = intent.getStringExtra(“chat_room_id”);

String title = intent.getStringExtra(“name”);

 

getSupportActionBar().setTitle(title);

getSupportActionBar().setDisplayHomeAsUpEnabled(true);

 

if (chatRoomId == null) {

Toast.makeText(getApplicationContext(), “Chat room not found!”, Toast.LENGTH_SHORT).show();

finish();

}

 

recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

 

messageArrayList = new ArrayList<>();

 

// self user id is to identify the message owner

String selfUserId = MyApplication.getInstance().getPrefManager().getUser().getId();

 

mAdapter = new ChatRoomThreadAdapter(this, messageArrayList, selfUserId);

 

LinearLayoutManager layoutManager = new LinearLayoutManager(this);

recyclerView.setLayoutManager(layoutManager);

recyclerView.setItemAnimator(new DefaultItemAnimator());

recyclerView.setAdapter(mAdapter);

 

mRegistrationBroadcastReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

if (intent.getAction().equals(Config.PUSH_NOTIFICATION)) {

// new push message is received

handlePushNotification(intent);

}

}

};

 

btnSend.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

sendMessage();

}

});

 

fetchChatThread();

}

 

@Override

protected void onResume() {

super.onResume();

 

// registering the receiver for new notification

LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,

new IntentFilter(Config.PUSH_NOTIFICATION));

 

NotificationUtils.clearNotifications();

}

 

@Override

protected void onPause() {

LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);

super.onPause();

}

 

/**

* Handling new push message, will add the message to

* recycler view and scroll it to bottom

* */

private void handlePushNotification(Intent intent) {

Message message = (Message) intent.getSerializableExtra(“message”);

String chatRoomId = intent.getStringExtra(“chat_room_id”);

 

if (message != null && chatRoomId != null) {

messageArrayList.add(message);

mAdapter.notifyDataSetChanged();

if (mAdapter.getItemCount() > 1) {

recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, null, mAdapter.getItemCount() – 1);

}

}

}

 

/**

* Posting a new message in chat room

* will make an http call to our server. Our server again sends the message

* to all the devices as push notification

* */

private void sendMessage() {

final String message = this.inputMessage.getText().toString().trim();

 

if (TextUtils.isEmpty(message)) {

Toast.makeText(getApplicationContext(), “Enter a message”, Toast.LENGTH_SHORT).show();

return;

}

 

String endPoint = EndPoints.CHAT_ROOM_MESSAGE.replace(“_ID_”, chatRoomId);

 

Log.e(TAG, “endpoint: ” + endPoint);

 

this.inputMessage.setText(“”);

 

StringRequest strReq = new StringRequest(Request.Method.POST,

endPoint, new Response.Listener<String>() {

 

@Override

public void onResponse(String response) {

Log.e(TAG, “response: ” + response);

 

try {

JSONObject obj = new JSONObject(response);

 

// check for error

if (obj.getBoolean(“error”) == false) {

JSONObject commentObj = obj.getJSONObject(“message”);

 

String commentId = commentObj.getString(“message_id”);

String commentText = commentObj.getString(“message”);

String createdAt = commentObj.getString(“created_at”);

 

JSONObject userObj = obj.getJSONObject(“user”);

String userId = userObj.getString(“user_id”);

String userName = userObj.getString(“name”);

User user = new User(userId, userName, null);

 

Message message = new Message();

message.setId(commentId);

message.setMessage(commentText);

message.setCreatedAt(createdAt);

message.setUser(user);

 

messageArrayList.add(message);

 

mAdapter.notifyDataSetChanged();

if (mAdapter.getItemCount() > 1) {

// scrolling to bottom of the recycler view

recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, null, mAdapter.getItemCount() – 1);

}

 

} else {

Toast.makeText(getApplicationContext(), “” + obj.getString(“message”), Toast.LENGTH_LONG).show();

}

 

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “json parse error: ” + e.getMessage(), Toast.LENGTH_SHORT).show();

}

}

}, new Response.ErrorListener() {

 

@Override

public void onErrorResponse(VolleyError error) {

NetworkResponse networkResponse = error.networkResponse;

Log.e(TAG, “Volley error: ” + error.getMessage() + “, code: ” + networkResponse);

Toast.makeText(getApplicationContext(), “Volley error: ” + error.getMessage(), Toast.LENGTH_SHORT).show();

inputMessage.setText(message);

}

}) {

 

@Override

protected Map<String, String> getParams() {

Map<String, String> params = new HashMap<String, String>();

params.put(“user_id”, MyApplication.getInstance().getPrefManager().getUser().getId());

params.put(“message”, message);

 

Log.e(TAG, “Params: ” + params.toString());

 

return params;

};

};

 

 

// disabling retry policy so that it won’t make

// multiple http calls

int socketTimeout = 0;

RetryPolicy policy = new DefaultRetryPolicy(socketTimeout,

DefaultRetryPolicy.DEFAULT_MAX_RETRIES,

DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);

 

strReq.setRetryPolicy(policy);

 

//Adding request to request queue

MyApplication.getInstance().addToRequestQueue(strReq);

}

 

 

/**

* Fetching all the messages of a single chat room

* */

private void fetchChatThread() {

 

String endPoint = EndPoints.CHAT_THREAD.replace(“_ID_”, chatRoomId);

Log.e(TAG, “endPoint: ” + endPoint);

 

StringRequest strReq = new StringRequest(Request.Method.GET,

endPoint, new Response.Listener<String>() {

 

@Override

public void onResponse(String response) {

Log.e(TAG, “response: ” + response);

 

try {

JSONObject obj = new JSONObject(response);

 

// check for error

if (obj.getBoolean(“error”) == false) {

JSONArray commentsObj = obj.getJSONArray(“messages”);

 

for (int i = 0; i < commentsObj.length(); i++) {

JSONObject commentObj = (JSONObject) commentsObj.get(i);

 

String commentId = commentObj.getString(“message_id”);

String commentText = commentObj.getString(“message”);

String createdAt = commentObj.getString(“created_at”);

 

JSONObject userObj = commentObj.getJSONObject(“user”);

String userId = userObj.getString(“user_id”);

String userName = userObj.getString(“username”);

User user = new User(userId, userName, null);

 

Message message = new Message();

message.setId(commentId);

message.setMessage(commentText);

message.setCreatedAt(createdAt);

message.setUser(user);

 

messageArrayList.add(message);

}

 

mAdapter.notifyDataSetChanged();

if (mAdapter.getItemCount() > 1) {

recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, null, mAdapter.getItemCount() – 1);

}

 

} else {

Toast.makeText(getApplicationContext(), “” + obj.getJSONObject(“error”).getString(“message”), Toast.LENGTH_LONG).show();

}

 

} catch (JSONException e) {

Log.e(TAG, “json parsing error: ” + e.getMessage());

Toast.makeText(getApplicationContext(), “json parse error: ” + e.getMessage(), Toast.LENGTH_SHORT).show();

}

}

}, new Response.ErrorListener() {

 

@Override

public void onErrorResponse(VolleyError error) {

NetworkResponse networkResponse = error.networkResponse;

Log.e(TAG, “Volley error: ” + error.getMessage() + “, code: ” + networkResponse);

Toast.makeText(getApplicationContext(), “Volley error: ” + error.getMessage(), Toast.LENGTH_SHORT).show();

}

});

 

//Adding request to request queue

MyApplication.getInstance().addToRequestQueue(strReq);

}

 

}

Now deploy the app on two different devices and try sending the messages in a chat room. The other device should start receiving the messages. You can also use the admin panel http://localhost/gcm_chat to send the messages to both the devices. You can also minimize the app and check the new messages in notification bar.

android-realtime-chat-app-discussion-screen-self-other-messages

Live Demo

Here is the Live Demo of the app where you can download the apk and test it from the admin panel. Note that this apk is signed with my GCM API Key. So the live demo works only with the apk I have provided.