Android Building Realtime Chat App using GCM, PHP & MySQL – Part 3
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.
Android Building Realtime Chat App using GCM, PHP & MySQL – Part 3
By Ravi Tamada July 12, 2017 225 CommentsIn 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.
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.
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.
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.
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;
}
}
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;
}
}
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.
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.
package
info.androidhive.gcm.app;
public
class
EndPoints {
// localhost url -
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.
<
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.
<?
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.
<?
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.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
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.
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
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.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
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.
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.
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.
<
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.
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.
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.
<?
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.
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.
<?
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.
<?
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.
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
>
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.
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.
<?
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.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
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.
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]
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK