9

Android Building Realtime Chat App using GCM, PHP & MySQL – Part 3

 3 years ago
source link: https://www.androidhive.info/2016/02/android-push-notifications-using-gcm-php-mysql-realtime-chat-app-part-3/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

App

Android Building Realtime Chat App using GCM, PHP & MySQL – Part 3

By Ravi Tamada July 12, 2017 225 Comments

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.

VIDEO DEMO

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.

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. Open build.gradle located under app directory and add volley dependency and rebuild the project.

build.gradle
dependencies {
...
compile 'com.mcxiaoke.volley:library-aar:1.0.0'
}

2. Open MyApplication.java located 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);
}
}

3. Create a package named model. Inside model package, create three classes named User.java, Message.java and 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;
}
}

4. Open MyPreferenceManager.java and 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();
}
}

5. Create EndPoints.java in 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";
}

6. Open GcmIntentService.java located 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 in MySQL 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.

7. Open strings.xml located 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>

8. Open colors.xml and 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>

9. Create an activity named LoginActivity.java by right clicking on activity ⇒ New ⇒ Activity ⇒ Blank Activity. This also creates activity_login.xml and content_login.xml layout files for login activity.

10. Open activity_login.xml and 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"
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>

11. Open activity_login.xml and 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"
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>

12. Open LoginActivity.java and 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 and MainActivity 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;
}
}
}
}

13. Open AndroidManifest.xml and make LoginActivity as Launcher activity.

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

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.

14. Open build.gradle and add the RecyclerView dependency 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"
}

15. Create a layout named chat_rooms_list_row.xml. This layout renders a single chat room information like chat room name, last message, unread message count and the timestamp in 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>

16. Create a new package named adapter. Under adapter create a class named ChatRoomsAdapter.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) {
}
}
}

17. Open MyGcmPushReceiver.java and modify the code as below. Here I have added few methods to handle push notification message. First the push notification will be identified by flag node 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);
}
}

18. Create an xml layout named line_divider.xml inside drawable folder.

<?xml version="1.0" encoding="utf-8"?>
android:shape="rectangle">
<size
android:width="1dp"
android:height="1dp" />
<solid android:color="@color/list_divider" />
</shape>

19. Create an xml file named bg_circle.xml inside drawable folder. This layout acts as circular background for unread message count.

bg_circle.xml
<solid android:color="@color/colorPrimary" />
<corners
android:bottomLeftRadius="20dp"
android:bottomRightRadius="20dp"
android:topLeftRadius="20dp"
android:topRightRadius="20dp" />
</shape>

20. Under helper package, create SimpleDividerItemDecoration.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);
}
}
}

21. Open menu_main.xml located under res menu and add a logout menu item to provide logout option to user.

menu_main.xml
tools:context=".activity.MainActivity">
<item
android:id="@+id/action_logout"
android:orderInCategory="100"
android:title="@string/action_logout"
app:showAsAction="never" />
</menu>

22. Open content_main.xml and add the recycler view element.

content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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>

23. Finally open MainActivity.java and 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 which handlePushNotification() 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 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.

24. Create an xml file named bg_bubble_white.xml inside drawable folder. This layout adds a rounded corner background to other’s messages.

bg_bubble_white.xml
<?xml version="1.0" encoding="utf-8"?>
<shape
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>

25. Create an xml file named bg_bubble_gray.xml inside drawable folder. This layout adds a rounded corner background to self messages.

bg_bubble_gray.xml
<?xml version="1.0" encoding="utf-8"?>
<shape
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>

26. Create two xml layouts named chat_item_self.xml and chat_item_other.xml to render both self and other messages.

chat_item_self.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
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>

27. Create a class named ChatRoomThreadAdapter.java under adapter package.

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;
}
}

28. Create a new activity class named ChatRoomActivity.java by right clicking on activity ⇒ New ⇒ Activity ⇒ Blank Activity. This creates two layout files named activity_chat_room.xml and 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"
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>

29. Open content_chat_room.xml and 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"
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>

30. Open ChatRoomActivity.java modify 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.

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.

Hi there! I am Founder at androidhive and programming enthusiast. My skills includes Android, iOS, PHP, Ruby on Rails and lot more. If you have any idea that you would want me to develop? Let’s talk: [email protected]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK