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.

android realtime chat app using gcm php mysql

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

<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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="info.androidhive.gcm.activity.LoginActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

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

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

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

11. Open activity_login.xml and create a simple login form with name and email input fields.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="info.androidhive.gcm.activity.LoginActivity"
    tools:showIn="@layout/activity_login">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:id="@+id/input_layout_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/input_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/hint_name" />

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

        <android.support.design.widget.TextInputLayout
            android:id="@+id/input_layout_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/input_email"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/hint_email" />

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

        <Button android:id="@+id/btn_enter"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Enter" />

    </LinearLayout>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center"
        android:layout_marginBottom="10dp"
        android:indeterminateTint="@color/colorAccent"
        android:visibility="gone"
        android:indeterminateTintMode="src_atop" />

</RelativeLayout>

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.

android-realtime-chat-app-login-screen

Adding Chat Rooms List Screen

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

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

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"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <size
        android:width="1dp"
        android:height="1dp" />

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

</shape>

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

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

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

    <corners
        android:bottomLeftRadius="20dp"
        android:bottomRightRadius="20dp"
        android:topLeftRadius="20dp"
        android:topRightRadius="20dp" />

</shape>

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.

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

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:showIn="@layout/activity_main"
    tools:context=".activity.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbars="vertical" />
</RelativeLayout>

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.

android realtime chat app login screen

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

    <!-- view background color -->
    <solid
        android:color="@android:color/white" >
    </solid>



    <!-- If you want to add some padding -->
    <padding
        android:left="10dp"
        android:top="4dp"
        android:right="10dp"
        android:bottom="4dp"    >
    </padding>

    <!-- Here is the corner radius -->
    <corners
        android:radius="5dp"   >
    </corners>

</shape>

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

    <!-- view background color -->
    <solid
        android:color="@color/bg_bubble_self" >
    </solid>



    <!-- If you want to add some padding -->
    <padding
        android:left="10dp"
        android:top="4dp"
        android:right="10dp"
        android:bottom="4dp"    >
    </padding>

    <!-- Here is the corner radius -->
    <corners
        android:radius="5dp"   >
    </corners>

</shape>

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

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="right"
    android:orientation="horizontal"
    android:paddingBottom="5dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp">

    <TextView
        android:id="@+id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:textIsSelectable="true"
        android:background="@drawable/bg_bubble_gray"
        android:textSize="14dp" />

    <TextView
        android:id="@+id/timestamp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@id/message"
        android:layout_below="@id/message"
        android:layout_marginBottom="25dp"
        android:padding="10dp"
        android:textSize="10dp" />

</RelativeLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:paddingTop="5dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp">

    <TextView
        android:id="@+id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="10dp"
        android:textIsSelectable="true"
        android:background="@drawable/bg_bubble_white"
        android:textSize="14dp" />

    <TextView
        android:id="@+id/timestamp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/message"
        android:layout_marginBottom="25dp"
        android:layout_marginLeft="10dp"
        android:paddingLeft="10dp"
        android:paddingTop="6dp"
        android:textSize="10dp" />
</RelativeLayout>

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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".activity.ChatRoomActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

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

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

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

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"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".activity.ChatRoomActivity"
    tools:showIn="@layout/activity_chat_room">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="20dp"
        android:scrollbars="vertical" />

    <LinearLayout
        android:background="@android:color/white"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal"
        android:weightSum="4">

        <EditText android:id="@+id/message"
            android:layout_width="0dp"
            android:hint="Enter message"
            android:paddingLeft="10dp"
            android:background="@null"
            android:layout_marginRight="10dp"
            android:layout_marginLeft="16dp"
            android:lines="1"
            android:layout_height="wrap_content"
            android:layout_weight="3" />

        <Button android:id="@+id/btn_send"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@null"
            android:text="SEND"
            android:textSize="16dp"
            android:textColor="@color/colorPrimary" />

    </LinearLayout>

</RelativeLayout>

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.

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

Live Demo

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

Ravi is hardcore Android programmer and Android programming has been his passion since he compiled his first hello-world program. Solving real problems of Android developers through tutorials has always been interesting part for him.
  • Yaqoub Alsaadi

    hi Ravi Tamada

    nice article…
    brother ,i am new developer,
    i want gcm notification…this article very complicated and has chat rooms!!!
    just i want to receive notification from GCM…
    plz help me…

    • In that case follow only the 2nd part. It is sufficient to integrate the gcm.

      • Tux Apple

        me too, i just want to learn how to client app receive notification from gcm

  • Anupam Anand

    brother getting this error
    Value true at error of type java.lang.Boolean cannot be converted to JSONObject
    while registering can u please tell why
    also my http://booksasap.in/gcm_chat/ is not showing all option please check and reply

  • Ajay

    Hi ravi, you are doing very well, so please keep doing your work, it help us to learn about new things in android technologies and thanks for sharing your knowledge with us.

    • Hi Ajay, I’ll help as much as I can 🙂

  • abdullah

    Thanks for your great work , you are filling some big gab in this area ….. something here that when I open the chatting room there is scrolling starting from the first line until the end of the view ..the final line is hidden , plz how to prevent scrolling and show the final line . just go directly the last line , also do you recommend some open source chatting view that make our life easy and show conversations something like what “WhatsApp” does ?? thanks a lot ravi again

    • In order to show the last message which is hidden, adding padding bottom to recylcer view. To disable scrolling, replace smoothScrollToPosition with scroll to function without animation.

      I didn’t find any open source chat views, but you can buy one.

    • abdullah

      nother thing , there is something when I add many message from the chatting room , only the first message will be sent and shown each time ..

  • Hamdi wanis

    first of all i can not thank u enough for your great effort u r amazing really, but i have 3 questions if may i !
    1- why does not the new msgs counter displaying in the demo apk?
    2- how can i add chat rooms in real time?
    3- and the most important one, how can this be used as one to one chat?
    thanks again ravi 🙂

    • Here are the answers

      1- why does not the new msgs counter displaying in the demo apk?
      I guess the counter is working fine in demo apk as shown in the video. Let me know if I understood the question wrongly.

      2- how can i add chat rooms in real time?
      For this you need to add another method to rest api to create a new chat room. From the app pass the chat room name to rest api endpoint which inserts a new entry in chat_rooms table. After that you need to subscribe the user to newly created chat room channel in order to receive the push notifications.

      Ex: If newly inserted row primary key id 100, subscribe the user to topic_100.

      3- and the most important one, how can this be used as one to one chat?
      It is also same as 2nd point. Ex: let’s say user1 is trying to chat with user2. Then create a chat room with some name and subscribe both the users to that chat room channel. So that only these two users get the push notifications. Also in the chat rooms table you can add filed `visibility` with the values 1 or 0. If the value is 1, its public chat room, anyone can see. If the value 0, its a private chat room. Only the users who is involved can see that chat room.

      • Hamdi wanis

        yeah i downloaded the apk and tried the live demo but the counter wasn’t working but its not a big deal u done enough really and too grateful for u man thanks for your advice ill try it and keep u updated with the result 🙂

        • Cool!

          • Sheetal

            hi appp working fine…but notification see in toast but not as counter
            pls help

          • Toast message comes when a message is received to the user. The counter will increment if the message belongs to a chat room.

  • Mezooo

    you the best person i know him you are real brilliance super smart man thanks a lot great think to you

    • Thanks for the appreciation Mezooo.

      • Mezooo

        you deserve every word really thanks man.

  • Hamdi wanis

    hey ravi may i ask what this urls usage
    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”;
    they don’t exist in the project and it dont work without them ?

  • Vaishal Shah

    Hi Ravi,
    First of all Thanks for amazing tutorials. It has been very helpful.

    I have one doubt in gcm. How do I combine multiple GCM messages and show them as single notification? In your code, you have given same notification ID to each notification so probably you will only get one latest notification in your notifications bar but how do I get functionality like whatsapp does “5 messages from 3 conversation”?

    Thanks in advance.

    • Hi Vaishal, I already implemented what you are looking for. Download the demo apk and try sending messages from the admin panel. All the unread messages will be appended just like WhatsApp.

      I used SharedPreferences to store the unread the messages.

  • Hamdi wanis

    hey ravi how to update the main ui if i recived a push notification for a msg but didnt open the app then

  • Mezooo

    Hi Ravi,
    Great think to you for amazing tutorials.
    there are some files missed in a project
    in PHP project /gcm_chat/v1/user/login is missed no file called “login”
    just index.php
    please, check that I am waiting your response 😉

  • Raveen

    Hi, It is an great article about the chat, and however, as you called it is real time, I could not see in that way because there are few reasons. Firstly, it does not show that the user is typing, and it is impossible using GCM.Secondly, It has character limits as it payload 4Kb in GCM.

    As you have done a great job, you could suggest to eliminate aforementioned the limitations.

    Once again nice job.

    • Hi Raveen

      Your points are definitely valid. The intent of this article is to teach all the beginners integrating the GCM with a realtime scenario as there are no resources available on the internet particularly a chat app. Adding up your points make the article much bigger and difficult to understand.

      1. Firstly, it does not show that the user is typing, and it is impossible using GCM
      You can’t build a powerful chat app just by using GCM. You need to combine multiple technologies like sockets, xmpp etc.,

      2. Secondly, It has character limits as it payload 4Kb in GCM
      In this case you can notify the app about new message instead sending the complete message as payload. Once the app is notified, it can make a normal http call to download the heavy message.

  • Choirul MA

    Hi Ravi,
    This a great tutorial..
    I just finished this tutorial and getting a little error below

    FATAL EXCEPTION: main

    android.content.ActivityNotFoundException: Unable to find explicit activity class {id.mcadev.gcmchatdemo/id.mcadev.gcmchatdemo.gcm.GcmIntentService}; have you declared this activity in your AndroidManifest.xml?

    GcmIntentService.java not an activity isn’t it? but i dont know why it’s error.
    Please Help me 🙂

    • Have you added GcmIntentService as a service in manifest file as mentioned in the 2nd part of this article.

      • Choirul MA

        Hi Ravi,
        the problem was solved.
        I use wrong function startactivity(intentService) .. should be startservice(intentService) .. 😀

        • Okay 🙂

        • Selva Ganesh

          Hai could u tell me how to use get method in chrome postman because i am getting error ( Couldnot get any response)

  • Rishika Kapur

    Hello Ravi,

    I have followed the steps exactly mentioned in tutorial. But testing app on Step No. 13, I am getting error. Can you help with this please?

    This is the error:

    02-24 16:59:41.517 22623-24624/com.yive.gcm E/LoginActivity: params: {email=kapur.rishika@yahoo.com, name=Rishika}

    02-24 16:59:41.842 22623-22623/com.yive.gcm E/LoginActivity: Volley error: java.net.ConnectException: failed to connect to /192.168.0.103 (port 80) after 2500ms: isConnected failed: ECONNREFUSED (Connection refused), code: null

    Update:

    Hey I just fixed that.

    • Your device is not able to connect to your PC / Laptop over wifi using 192.168.0.103. Run ipconfig / ifconfig again to get the correct ip address.

      (Normally ip address changes if your pc is disconnected from wifi and connects again)

  • Daniel Koczuła

    I’m trying to use this app with WordPress. For now everythings is but when I’m trying to send an error to app with response:
    $response[“error”] = true;
    $response[“message”] = “Error”;
    echo json_encode($response);

    On my device I’m getting an error:
    Json parse error:Value true at error of type java.lang.Boolean cannot be converted to JSONObect.

    How to fix it?

    • Sanjeev Chintakindi

      Did u fix this issue?

  • kamiszczu

    Thanks for the tutorial, but I have a problem on app android when I can register: volley error null
    BasicNetwork.performRequest: Unexpected response code 404 for http: // my_ip_machine / gcm_chat / v1 / user / login
    Volley error: null, code: com.android.volley.NetworkResponse@204d8cd
    Thanks for the help

    Edit:
    When I test with Postman …/gcm_chat/v1/user/login post I get error 404 Not Found.

  • Mezooo

    AsynTask vs volley vs retrofit

    http://i.stack.imgur.com/3u6s6.png

    we need tutorial about retrofit it’s fast a lot than AsynTask and volley. specific in retrofit ver. 2 latest version

  • priyanka 123

    Hi Ravi, I am getting this error
    java.lang.ClassCastException: org.json.JSONArray cannot be cast to org.json.JSONObject

    at this line
    JSONObject chatRoomsObj = (JSONObject) (chatRoomsArray.get(i));

    Please help me

  • Thanks a lot Ravi..
    I was able to setup and test complete app by following the detailed steps that you provided. You have saved many hours of developers. Thanks for putting effort in making other’s life easy.

    • I am glad you enjoyed it very much. Do share and recommended others to read.

      Happy Coding!

  • Syed Sirajul Islam Anik

    Demo alerts were good :p

  • Faruqi Muhammad

    Hi Ravi,
    Thanks for the tutorial, in all the tutorials I have tried, in the second part, the push test was successful and I continue with part three. but when I try to send the chat room, text gets in on the database (but must be refreshed), but the notification does not appear or does not work. where the possibility of errors?
    thank you so much

    • Check LogCat for errors. There might a json parsing error.

      • Faruqi Muhammad

        Could I get your email?

  • kamiszczu

    Thanks Ravi for great tutorial

    but tell me why I get this error: ** Attempt to invoke virtual method ‘helper.MyPreferenceManager app.MyApplication.getPrefManager()’ on a null object reference** ?

    Thanks for help.

    • It seems you forgot to add MyApplication to tag in AndroidManifest.xml

  • Sri Vivek

    I cannot use get_result() function in php ,,

    public function getAllChatRooms()
    {
    $Statement = $this->conn->prepare( ‘SELECT chat_room_id,name from chat_rooms’ );
    $Statement->execute();
    $Statement->bind_result($chat_room_id,$name);
    //$RESULT = get_result( $Statement );
    return $Statement;

    //$sql=”SELECT * FROM chat_rooms”;
    //$result=mysqli_query($this->conn,$sql);
    //return $result;

    //$stmt = $this->conn->prepare(“SELECT * FROM chat_rooms”);
    //$stmt->execute();
    //$tasks = $stmt->get_result();
    //$stmt->close();
    //return $result;
    }

    i tried different alternatives
    im unble to return result to the index.php file to display chat room ,,
    kindly help me ,,
    thanks in advance

    • Is there any error throwing?

      • Sri Vivek

        Fatal error: Call to undefined method mysqli_stmt::get_result() in
        gcm_chat/demo.php on line 27

        • John Doe

          You need to install the MySQL Native Driver (msqlnd) on your server

          • Sri Vivek

            so can u plaese provide me the link on tutorial or can u plese tell me how to install sqlnd driver on server

  • Bianca

    Hello Ravi!
    I am developing an android app that requires an instant chat. Does the chat that you created in this tutorial work on different networks or does it work only in LAN? Thanks!

  • Sajid Khan

    Thanks for your nice tutorials. I am not getting run time notification which is sending from other devices who are registered in my server. But getting some notifications which are sending by other users. I am using my web server and using your php and android code.

    • Faris Laziz

      have u found the solution for this problem?it happen to me also..help plss

      • Sajid Khan

        No, I didn’t. I send him a mail also but didn’t get any reply.

        • Faris Laziz

          i have found the solution…u try check your google-json and API Key in your config.php…

          • Vajeng Patidar

            Hi please will you helpp me to put API key in config.php

  • Aldhi

    Hy Ravi, can you help me, I have problem in Map params = new HashMap();

    diamond operator is not supported in -source 1.6

    (use -source 7 or higher to enable diamond operator)

    where is the source, must be change?

  • Eduardo Herrera

    Ravi i have a problem, when i try to register my device to the server i got Voller error:null

    • Raj Kr

      check ur internet connectivity…my be u connected to internet but no internet connectivity, then this type of error…becoz server not respone this time

  • Can you plz implement sending and receiving of images through this tutorial

  • m. nusky

    hi ravi, when I login I’m getting a volley error:null how to fix it?
    If I change the json file with my project id and number I get json parser error saying ” Value true at error of type java.lang.Boolean cannot be converted to JSONObject””.

    • Sanjeev Chintakindi

      Did u solved this?Login volley null error

  • Wysnu

    Thank you very much for your great work 🙂

  • Can Talay

    are there some files missed in a project ?

    in PHP project /gcm_chat/v1/user/login is missed no file called “login”

    just index.php
    thanks for work.

  • Faris Laziz

    hi ravi, when i running at hardware device…this error come out…where is my mistake?

    FATAL EXCEPTION: main

    Process: gcm.project.psisjob, PID: 20157

    java.lang.RuntimeException: Unable to start activity ComponentInfo{gcm.project.psisjob/gcm.project.psisjob.activity.LoginActivity}: java.lang.NullPointerException: Attempt to invoke virtual method ‘java.lang.CharSequence android.support.v7.widget.Toolbar.getTitle()’ on a null object reference

    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java)

  • Faris Laziz

    Please Help..when i run use hardware device..where is my mistake???

    FATAL EXCEPTION: main

    Process: gcm.project.psisjob, PID: 4088

    java.lang.RuntimeException: Unable to start activity ComponentInfo{gcm.project.psisjob/gcm.project.psisjob.activity.LoginActivity}: java.lang.NullPointerException: Attempt to invoke virtual method ‘java.lang.CharSequence android.support.v7.widget.Toolbar.getTitle()’ on a null object reference

    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java)

  • pavan

    Hi Ravi, there are two different resource files with the same name which is a lil confusing.Please check?
    *activity_login.xml*

    • justDisqus

      Step 10: activity_login.xml
      Step 11: content_login.xml

  • Sid Shehxad

    Hey Ravi, Hows you ? Just completed this tutorial. It was an awesome tutorial but i am having as issue. When i send a message in a group like Google Cloud Messaging it shows an exception Jsonparse error:value <br of type java.lang.String can not be converted to json object.

  • Harshit

    AWS tutorial please!!!!

  • Ashley

    Hi Ravi,

    I’ve noticed there is a delay with messages showing up on the app,
    You can send a message from the app but if you go out of the topic then back in the message you’ve sent isnt there anymore until about 10 – 15 minutes later, can you help me work out why or show me how to implement your “Android Swipe Down to Refresh ListView Tutorial” into the app please.

    Thanks,

    • Jonathan

      I also got a delay like you, did you fix it ? if yes how to fix the delay?

  • Thato Rammoko

    Hi Ravi thank you for this awesome tutorial I’m using Digital Ocean to host my server so when I send a message from the admin panel you provided us with I get an alert error message that says ” “xxx.yyy.zzz.aaa says, sorry unable to send message” even when I send a message from the app I get a toast saying “Failed to send Message”. Please Help.

  • RJZ

    Hi Ravi, nice post. I have some questions and I hope you can help me with.

    First, why do we need to send the registration token to the server use Request.Method.PUT? Can we just send registration token by upstream message use the GCM?
    Second, similar to the first one, when sending chat message you use Request.Method.PUT as well. We should be able to send upstream message as well, isn’t it?
    Just to be clear, is the sole purpose of using GCM in this application is for sending push notification?

    Thanks for your time!

  • Gon Her

    Error Volley null

    • AnthonyHaddad

      Check your android manifest; if its possible, copy and paste it here.

      • Gon Her

        There is an error but I can not detect. Everything is working fine except push notifications. When you open the app, I get a toast with error volleyball. Then load the chat screen correctly.

        I’m trying on my server online

        The client project is the same Ravi Tomada. All equal

        Thank you very much.

        • Tron

          How u get rid of volley error null pls mention here

        • Sergio Cordova

          which the solution??? plz i have de same problem 🙁

    • ChristosP

      Firewall rules. Permit traffic to the web port.

    • justDisqus

      I had the same error. Mine was because I was entering the name wrong (case sensitive)

    • missQ

      turn off your firewall and try again

  • AnthonyHaddad

    hello Ravi,

    when running the app on a physical device, im not able to send a message to single user

  • Gon Her

    Everything is working well for me.

    Less push notifications.

    Push Notification does not work.

    Send the notification. But does not reach the phone.

    Chat ok.

    help

    Image
    http://i.imgur.com/l2B3tSl.png

  • Bala Murali

    @ravi8x:disqus Hi, Ravi thanks alot for the tutorial. can i request for this app tutorial with MQTT protocol for messaging?

    • Sorry Bala, I don’t have good knowledge on MQTT protocol.

      • Geraldo K Fillipus

        Hi Ravi Here is what i am getting when connecting to server on cpenal
        “Warning: mysqli::mysqli(): php_network_getaddresses: getaddrinfo failed: Name or service not known in /home/logicmes/public_html/demo/include/db_connect.php on line 22

        Warning: mysqli::mysqli(): (HY000/2002): php_network_getaddresses: getaddrinfo failed: Name or service not known in /home/logicmes/public_html/demo/include/db_connect.php on line 22

        Failed to connect to MySQLi: php_network_getaddresses: getaddrinfo failed: Name or service not known

        Warning: mysqli::prepare(): Couldn’t fetch mysqli in /home/logicmes/public_html/demo/demo.php on line 39

        Fatal error: Call to a member function bind_param() on a non-object in /home/logicmes/public_html/demo/demo.php on line 40

        Please help

  • David Batista Taboada

    Hi @Ravi Tamada, I have a query, you’ll excuse the mistranslation no English so Google translator is speaking to use.

    What emulator are using to see 3 instances in 3 different devices at the same time?

    Greetings from Bolivia and thank you for the article, I’ll use it as the basis for a college project 🙂

  • manasseh

    hey Ravi… i followed the tutorial strictly and i could install the app on my phone but i tried to log in it keeps giving a Toast “Volley error null”

    • Sahar Fathy

      same here did you fix it

    • Tron

      I am also getting the same problem God someone fix it pls 🙁

  • Milan Gajera

    Hi @Ravi really it’s work well. I got two problem first is when i send the message to particular user from localhost/gcm_chat then javascript dialog will appeared say sorry not sending.

    Second i was not getting a notification on my device.

  • Myscrap New

    hiii ravi…. i done a chat… message sending perfectly… but push notification not receiving… please help.. im reaching the deadline. thanks in advance

  • ArtyMorty

    Hey Ravi, another great tutorial.
    One question – I want to make a chat app that would check all the contacts from my phone and compare them to registered users on the server (ie. my contacts that installed the same chat app). Those contacts would then be saved in my device sqlitedb. I’ve tried to find a tutorial that would explain this, but I was unsuccesfull.
    Is there a simple way to do this.
    Thanks in advance

  • Fatah Zull

    Hi Ravi. so sad! message sending perfectly… but push notification not working when app was killed… please help. thanks in advance

  • Ji

    Thank you for the tutorial!
    I have a question.
    Can I use another language like Chinese? Is this support UTF-8?

  • Gaurav Singh

    Hi Ravi , I am implemented the all code you are provided but i run is give error in this part

    if (MyApplication.getInstance().getPrefManager().getUser() == null) {
    launchLoginActivity();
    }
    what i do please help me!!1
    can you send code its urgent my last submission of project.

    • ananymous

      same here!

  • Gaurav Singh
  • Umut Dönmez

    Thank you so much for the codes and tutorials . You are awesome 😀

  • missQ

    which username and email i should use to login?

  • Luthfi M Nabil

    When I send message to topic_1, it will sent to another topic like topic_2 but only in android and if i refresh the topic(Re open) the message will dissapear..
    know how to fix it?

    btw…. Thanks for the Tutorial, Ravi 😀

  • Vanessa Almond

    Hi Ravi , I implemented the all code you have provided but when i run it gives a runtime error which is a null pointer exception which points to this part

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

    launchLoginActivity();

    }

    what i do please help me!!1

    • Clarence

      you forgot to add MyApplication (application) in Manifest

  • Pooja Gupta

    I have a shared server n i have no idea how to change mysqli to which form so as to work codes can you please help with it any alternate form

  • PR_DIMPU

    Can any one give me an hint to change this to do chatting with individual users??

    • You need to send message to user channel not global nor topic channel.

      • தமிழன் மணிகண்டன் பிரபாகரன்

        user to user

        • Create a topic for each user and subscribe him to that channel. Example when user id 100, create a topic_100 and subscribe user to topic_100 channel. Use this channel to send messages between users.

          Another solution is create a chat room for both the users and send message to that chat room topic.

  • Mouadh Saidani

    Hi Ravi, thx for this great tutorial
    Im implemented the all code and it work good in the same wifi connexion but if i connect my device in another wifi i can’t login it keeps giving a Toast “Volley error null” !!
    Can you help me please !! and thx agan 🙂

    • Mouadh Saidani

      sameone can help me please !!

      • Tomas

        This is because your server is running in your local network, You will always need to be in the same network as your server is to make it work – or get an online server

        • Mouadh Saidani

          oh OK I understand now, thank you.

          Can you tell me what are the needed change when I put the server online

    • annonymous

      try replace the ip address

      • Mouadh Saidani

        the server side is the same, it works fine if I connect my android device in the same network, the problaime is when I connect the android part on another network

  • Sahar Fathy

    Hi Ravi, thx for this great tutorial
    i implemented the code but when i run it it gives me a “json parsing error: value true at error of type java.lang.boolean can’t be converted to jason object.”
    so how i can solve it?
    sorry for my bad english

  • bard

    Hi Ravi, first of all I want to thank you so much, you’re the best, the app is great and is fully functinating except for Messages my Messages are upside down in chat room, i tried to add mysql syntax ‘ORDER BY’ in db_handler but it wont to work, do you know how can I fix it? Thanks in advance

    • Oh ok. Actually the recyclerView is upside down. Please check the code in activity.

      • bard

        Thanks Ravi you’re the best!

      • தமிழன் மணிகண்டன் பிரபாகரன்

        which activity

  • dev

    hi Ravi, thanks for the post,
    Iam getting this
    8495-18989/info.androidhive.gcm E/Volley: [8348] BasicNetwork.performRequest: Unexpected response code 404 for http://mylocalhostat.com/gcm_chat/v1/user/login

    05-18 13:05:20.630 18495-18495/info.androidhive.gcm E/LoginActivity: Volley error: null, code: com.android.volley.NetworkResponse@35d0b2e4

  • dev

    can any help me,Iam getting error as

    E/Volley: [8347]BasicNetwork.performRequest: Unexpected response code 404 for http://android.dhobipachad.com/gcm_chat/v1/user/login

    and

    E/LoginActivity: Volley error: null, code: com.android.volley.NetworkResponse@f72fb67

  • Rege

    Hello,

    Please little help 🙁
    I add new menu option (Logout, +Users). I pressed onto the new menu item and loads the activity. If I were I press the back arrow on the title side then you logged out of the application. I can not find a solution to this. Please help me.

  • Mudit Sen

    FCM( firebase cloud messaging) replaced GCM . Can you please update this tutorial 🙂

    • AnthonyHaddad

      thats why the notification isnt working ??

  • Serhat Ömer RENÇBER

    hi ravi,

    when you send message to anyone, another user is getting message at Any active Windows(rooms)

    i am trying this code. i can not do. getting message should go to correct rooms. we should check chat_room_id

    pls help.

    NotificationUtils.java
    if (activeProcess.equals(context.getPackageName())) {

    // .

    try {
    Method m = activeProcess.getClass().getMethod(“isRoomId”,null);
    Toast.makeText(context.getApplicationContext(), m.toString(), Toast.LENGTH_SHORT).show();

    if (chatRoomId.equals(m.toString()))
    {
    isInBackground = false;

    }
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    }

    // isInBackground = false;

    }
    }

  • Serhat Ömer RENÇBER

    i added new method in ChatRoomActivity.
    } return chatRoomId; {public String isRoomId()

  • Funmite Kayode

    Please how can i get the IP address of my system,because it windows 8.1. When i try to use it give error that i server cannot be reached

  • raoua

    nice

  • Chauhan Ajay

    nice demo sir …but this is related with chat app…and its looks like more complicated..
    can you pls make simple one only Push Notification like,
    Android Google Cloud Messaging (GCM) Running PHP Project with new MySqli Query
    https://www.youtube.com/watch?v=cR45sih7T_M
    Please sir _/_

  • Chauhan Ajay

    Ravi sir you making so many tutorial for every one,
    can you please make new another tutorial on
    Android Google Cloud Messaging (GCM) Running PHP Project with new MySqli Query
    sir please don’t ignore me 🙁

  • Fahad Shah

    Hi

    great tutorial.

    I want to send image also with the text and when I send url link its turn into text.

    please help.

    • vinod

      Hi you can use spannable string builder and imagespan to do this

  • Santi Hidalgo

    Hello,
    I installed the “Android Building Realtime Chat App using GCM, PHP & MySQL” but in the chatrooms class, where all messages are loaded, placed on the list first SELF, and then others, and not ordained as such come in consultation.
    What I do wrong? … Or timing problem?
    Can you help me ?
    Thank you

  • vinod

    Hi is there any possible to add images and videos like whatsapp in this application?

  • Manish Shakya

    What is this Problem Sir Please Help

    java.lang.IncompatibleClassChangeError: The method ‘java.io.File android.support.v4.content.ContextCompat.getNoBackupFilesDir(android.content.Context)’ was expected to be of type virtual but instead was found to be of type direct (declaration of ‘com.google.android.gms.iid.zzd’ appears in /data/data/com.andideveloper.chat/files/instant-run/dex/slice-com.google.android.gms-play-services-gcm-8.3.0_296f4873918e6a85cef8acbd7478260fbe357862-classes.dex)

  • Manish Shakya

    Volley error: null, code: com.android.volley.NetworkResponse@7a72118

    • Fesi Nagetive

      Hi Manish Did you find any solution to this? I am facing the same problem

      • Manish Shakya

        yes..there is some error on php which you called

        • Sergio Cordova

          I have the same problem 🙁
          You have the solution???

        • Sergio Cordova

          friend throws me this error “unable to fetch topics mesagges”

    • Faraz Ahmed

      it is just a network connection url error.
      Go to ur Endpoints.java file and make sure that the BASE_URL is correct.
      For Example: “http:/192.160.4.4/(Your Server directory name where u have all of your php files)/v1”

      Make sure that u don end the url with a forward slash in the end
      Hope this may help you!

  • Manish Shakya

    When i have put all php files in server…
    Fatal error: Call to undefined method mysqli_stmt::get_result()
    can you please help me

    • Sergio Cordova

      you need install driver mysqlnd (:

  • Fesi Nagetive

    great tutorial but the messages donot preserve the state. when come back and forth from messages to room and then messages. all the messages from particular person moves up and messages from myside moves down irrespective of time when they are arrived.

    • Sergio Cordova

      you correct the problem volley null Error???

      • Peter Muchiri

        something wrong in your php code,mine was the verifyEmail function not working so i commented it out

        • Sergio Cordova

          friend throws me this error “unable to fetch topics mesagges” and it works all right by “localhost”, but when you get on a paid server does not work and throws the error mentioned. for what is this?

          • Peter Muchiri

            thats the issue am currently facing,will update soon i i get the solution

          • Peter Muchiri

            i gave up with my isp,so i resolved with this work around.This statement doest work,so replace it with a simple trick,see my getAllChatRooms method

            public function getAllChatRooms(){
            $stmt=mysqli_query($this->conn,”SELECT * FROM chat_rooms”);

            $tasks=array();
            while($row=mysqli_fetch_assoc($stmt)){
            array_push($tasks,$row);
            }
            $stmt->close();
            return $tasks;
            }

  • Munna

    Hi Ravi,
    on clicking any one of the chat room on android, no messages are displaying. But if i click on enter message all messages will appear. please help regarding this.

  • luqman baihaqi

    Ahh,… nice tutorial brother. I ve download the apk, and trying for coding with your zip file… i ve done with push message with web app (http://webkoe.id), and for now, trying to implement it on android… hope get lucky on this path 🙂

  • Wow. It’s 2016 and you’re still doing manual parsing of JSON.

    • Thanks man, but this article is for beginners.

      You can find the json serializer article here
      http://www.androidhive.info/2016/05/android-working-with-retrofit-http-library/

      • That is exactly the problem. Parsing JSON using a library is insanely easy and less error-prone for beginners. 🙂

        • jay

          Don’t think it is a smart idea to teach beginners about libraries as yet. Teach them how to process Raw Json first so they know what the libraries you talking about do under the hood.

          • Fair point. But I still feel that should go into a different tutorial. Using manual JSON parsing in every tutorial is not nice because then you’re essentially increasing the scope of your tutorials.

          • jay

            Oh agreed. Misunderstood.

  • Abeva

    Hi Ravi Great app
    but have bug in this app
    If you open different room on 2phones the message Sent from one to another room!!
    [ I’m tested Samsung S3 & LG g2]

  • Abeva

    Hi Ravi Great app
    but have bug in this app
    If you open different room on 2phones the message Sent from one to another room!!
    [ I’m using with Samsung S3 & LG g2]

  • Adedara Klever Olanrewaju

    Hey Ravi, do you have any tutorial on a private 1:1 chat in android?

    • It’s the same tutorial. You need to create a topic for two people and send messages to that topic.

      • Adedara Klever Olanrewaju

        OK Ravi, but I don’t want to be the one creating the topic I actually want the user to come online, available users and chat with them

        • I mean there will be a new topic created everytime a user registers. You can use that topic to send message personally or create a topic name when two people wants to chat.

  • Sanket

    I have error in webpage
    Fatal error: Call to undefined method mysqli_stmt::get_result()

  • can you please make a follow up tutorial for migrating this video series to new Firebase if possible.. thanks a lot for the tuts

  • Ashish Tripathi

    Where is the activity_main layout ??
    U forgot it

    • Binamra Kandel

      you could have downloaded the complete code provided by him. Anyway here is the activity_main layout.

  • Dada Jee

    How to set timestamp while saving data in simple firebase database and retrieve the timestamp from firebase database

    • deaspo

      Hi Jee, use HashMaps to set timestamps and retrieve

  • deaspo

    I have a chat app, built using Firebase and am stuck at setting push notifications for topics and individual users.chat. From the console I can send messages but I want to do it using FirebaseMessagingService

  • bro

    Hi can I share files in this application?

  • ZOOBAZIZ

    Hi ravi, I tried to modify the app to create chatrooms which has worked properly. I just need to know how i can send direct message to a specific user through the app.

    • You can use his registrationId.

      • ZOOBAZIZ

        ok.. also how do refresh the message list and chat room list without refreshing or closing the app?

        • Send a push notification when there is a new group created. Upon receiving the notification, send a request to server to fetch the recent group data and refresh the list.

  • AR PU

    Help me plz. . .
    I’ve followed everything from start to finish but there was an error at the end and the time the application runs it straight out, Please help

    https://uploads.disquscdn.com/images/185b13548d0f0f8ff69c19c61fe4e5c32f61787747e16fbb85fcbaf8ecd08b16.png

  • Chobela Kakumbi

    would be simpler if this tutorial was to be updated using firebase

  • Muneer Khan

    The app works fine but I have got into a new bug, it won’t send PUSH notifications and the sound however when I check the chats the messages are being sent successfully, anyone knows where the problem is?

  • ar ta

    Hi ravi,how can i get sended message status like user read message or unread,deliverd or not?

  • Sharath Kumar A

    Sir, Whether this tutorial is working with FCM(Firebase Clous Messaging)??

  • Vinu Chacko

    Hi Ravi, Can you please upgrade this tutorial from GCM to FCM.?

  • Vinu Chacko

    Hai Sir.
    I followed your tutorial about chat –
    aroidhive.info/2016/02/android-push-notifications-using-gcm-php-mysql-realtime-chat-app-part-3/

    And Now I change it to FCM , But the problem is , Recycler view row item has the maximum width (Same as the screen size)
    How Can I chang this?

    • alaa

      Hi , can you please tell us what changes you made to convert to FCM

  • Ani

    Hello,
    Thank you for this nice tutorial, I’ve a strange issue, the app runs perfectly fine on local machine (XAMPP), however when I put it on the server (shared hosting), the messages I send are not shown.

    When I enter a chatroom, type in a message and hit send, I can see the message, it’s even stored in the DB, but when I go back and enter the chat room again the message is not shown. The GET request does not get the updated(sent message) JSON string, but strangely when I use postman or any other browser, the same GET request give me the complete JSON string.

    Bu it works fine on my local machine. Any suggestion?

  • Kevin.c Ciang

    Hello @ravi8x:disqus I have followed all your tutorial. The chat program works fine, but when I send a message, there is no notification on the recipient device. Please help me, Thank you.

  • Hey Ravi,
    Your tutorials are always helpful for me
    i am making app in which users should comment feedback to shops and shop should have option to reply user same as play store where developer can reply user’s feedback
    do you have any tutorial on it?
    or can you help me out in this?
    You are always thankful, i hope you will help me out this time also.

  • Pier Summov

    Hi, that’s very useful app example. I use FCM instead and there’s the same method onRecive. Now… I have different room. How I Can differently recive message? If i stay in a room, I want see directory new message incominciare, bit if i stay in other room i need to see notificato in on top…What strings of your code dò this? Ty

  • Minhaj Sayyad

    Hi sir ,
    Live demo of this tutorial give an error.

    Warning: mysqli::mysqli(): (HY000/2002): Connection refused in /var/www/html/demos/gcm_chat/include/db_connect.php on line 24
    Failed to connect to MySQL: Connection refused
    Warning: mysqli::prepare(): Couldn’t fetch mysqli in /var/www/html/demos/gcm_chat/demo.php on line 41

    Fatal error: Call to a member function bind_param() on a non-object in /var/www/html/demos/gcm_chat/demo.php on line 42

  • Poli Klo

    Hi Vinu Chacko,

    If possible you can send modify chat source (FCM) ? Please,

    Thank you for your help!

    Rege