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.

Subscribe
Notify of
guest
225 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Yaqoub Alsaadi
Yaqoub Alsaadi
4 years ago

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…

Ravi Tamada
4 years ago
Reply to  Yaqoub Alsaadi

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

Tux Apple
Tux Apple
4 years ago
Reply to  Ravi Tamada

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

Anupam Anand
Anupam Anand
4 years ago

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
Ajay
4 years ago

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.

Ravi Tamada
4 years ago
Reply to  Ajay

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

abdullah
abdullah
4 years ago

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

Ravi Tamada
4 years ago
Reply to  abdullah

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
abdullah
4 years ago
Reply to  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
Hamdi wanis
4 years ago

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 🙂

Ravi Tamada
4 years ago
Reply to  Hamdi wanis

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
Hamdi wanis
4 years ago
Reply to  Ravi Tamada

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 🙂

Ravi Tamada
4 years ago
Reply to  Hamdi wanis

Cool!

Sheetal
Sheetal
4 years ago
Reply to  Ravi Tamada

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

Ravi Tamada
4 years ago
Reply to  Sheetal

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

Mezooo
Mezooo
4 years ago

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

Ravi Tamada
4 years ago
Reply to  Mezooo

Thanks for the appreciation Mezooo.

Mezooo
Mezooo
4 years ago
Reply to  Ravi Tamada

you deserve every word really thanks man.

Hamdi wanis
Hamdi wanis
4 years ago

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 ?

Ravi Tamada
4 years ago
Reply to  Hamdi wanis

These are the php project urls we trying to hit from the app. Check I am using these urls whenever I am making volley http call. For example in MainActivity.java I am using EndPoints.USER in sendRegistrationToServer() method.

Hamdi wanis
Hamdi wanis
4 years ago
Reply to  Ravi Tamada

but we did not make such php files only file in v1 folder we made is index

Hamdi wanis
Hamdi wanis
4 years ago
Reply to  Ravi Tamada

it just im getting volley : null and the log have /user/login url

Mezooo
Mezooo
4 years ago
Reply to  Ravi Tamada

the project is not crash for paths or the not exist files it’s crash because you should run it on PHP 5.4 or above and mysqlnd enable

the problem now i have PHP 5.4 but mysqlnd disable and i don’t know how to enable it.

Ravi Tamada
4 years ago
Reply to  Mezooo

If you are using a shared hosting you can’t install mysqlnd. If you think your server is in your control, you have to install complete php with mysqlnd support or just the mysqlnd module.

http://stackoverflow.com/questions/23158943/install-both-mysql-and-mysqlnd-on-ubuntu-12-04

Mezooo
Mezooo
4 years ago
Reply to  Ravi Tamada

yes I am on shared hosting, but I am looking on other server to be full control on it.
Are you have any suggestions ?? for fast cool servers to get it

Ravi Tamada
4 years ago
Reply to  Mezooo

May be you can try paid hosting like DigitalOcean. It’s super fast and cheap.
http://www.androidhive.info/2014/01/how-to-create-rest-api-for-android-app-using-php-slim-and-mysql-day-12-2/

Mezooo
Mezooo
4 years ago
Reply to  Ravi Tamada

Great thanks I have a bluehost shared server i think it’s time to change to DigitalOcean. for complete control.

Vaishal Shah
Vaishal Shah
4 years ago

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.

Ravi Tamada
4 years ago
Reply to  Vaishal Shah

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
Hamdi wanis
4 years ago

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

Mezooo
Mezooo
4 years ago

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
Raveen
4 years ago

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.

Ravi Tamada
4 years ago
Reply to  Raveen

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
Choirul MA
4 years ago

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 🙂

Ravi Tamada
4 years ago
Reply to  Choirul MA

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

Choirul MA
Choirul MA
4 years ago
Reply to  Ravi Tamada

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

Ravi Tamada
4 years ago
Reply to  Choirul MA

Okay 🙂

Selva Ganesh
Selva Ganesh
4 years ago
Reply to  Choirul MA

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

Choirul MA
Choirul MA
4 years ago
Reply to  Selva Ganesh

Hi Selva,, Sorry for late respons..
Take this screenshots http://prntscr.com/a9azg6

Rishika Kapur
Rishika Kapur
4 years ago

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.

Ravi Tamada
4 years ago
Reply to  Rishika Kapur

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
Daniel Koczuła
4 years ago

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
Sanjeev Chintakindi
3 years ago

Did u fix this issue?

kamiszczu
kamiszczu
4 years ago

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
Mezooo
4 years ago

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
priyanka 123
4 years ago

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

Lokendra Kushwah
4 years ago

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.

Ravi Tamada
4 years ago

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

Happy Coding!

Syed Sirajul Islam Anik
Syed Sirajul Islam Anik
4 years ago

Demo alerts were good :p

Faruqi Muhammad
Faruqi Muhammad
4 years ago

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

Ravi Tamada
4 years ago

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

Faruqi Muhammad
Faruqi Muhammad
4 years ago
Reply to  Ravi Tamada

Could I get your email?

Ravi Tamada
4 years ago

ravi8x[at]gmail.com

kamiszczu
kamiszczu
4 years ago

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.

Ravi Tamada
4 years ago
Reply to  kamiszczu

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

kamiszczu
kamiszczu
4 years ago
Reply to  Ravi Tamada

Its works! Thanks 🙂

Ravi Tamada
4 years ago
Reply to  kamiszczu

🙂

Sri Vivek
Sri Vivek
4 years ago

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

Ravi Tamada
4 years ago
Reply to  Sri Vivek

Is there any error throwing?

Sri Vivek
Sri Vivek
4 years ago
Reply to  Ravi Tamada

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

John Doe
John Doe
4 years ago
Reply to  Sri Vivek

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

Sri Vivek
Sri Vivek
4 years ago
Reply to  John Doe

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

Bianca
Bianca
4 years ago

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
Sajid Khan
4 years ago

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
Faris Laziz
4 years ago
Reply to  Sajid Khan

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

Sajid Khan
Sajid Khan
4 years ago
Reply to  Faris Laziz

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

Faris Laziz
Faris Laziz
4 years ago
Reply to  Sajid Khan

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

Vajeng Patidar
Vajeng Patidar
4 years ago
Reply to  Faris Laziz

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

Aldhi
Aldhi
4 years ago

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
Eduardo Herrera
4 years ago

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

Raj Kr
Raj Kr
4 years ago

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

Evan Dantas
4 years ago

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

m. nusky
m. nusky
4 years ago

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
Sanjeev Chintakindi
3 years ago
Reply to  m. nusky

Did u solved this?Login volley null error

Wysnu
Wysnu
4 years ago

Thank you very much for your great work 🙂

Can Talay
Can Talay
4 years ago

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
Faris Laziz
4 years ago

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
Faris Laziz
4 years ago

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
pavan
4 years ago

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

justDisqus
justDisqus
4 years ago
Reply to  pavan

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

Sid Shehxad
Sid Shehxad
4 years ago

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
Harshit
4 years ago

AWS tutorial please!!!!

Ashley
Ashley
4 years ago

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
Jonathan
4 years ago
Reply to  Ashley

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

Thato Rammoko
Thato Rammoko
4 years ago

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
RJZ
4 years ago

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
Gon Her
4 years ago

Error Volley null

AnthonyHaddad
AnthonyHaddad
4 years ago
Reply to  Gon Her

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

Gon Her
Gon Her
4 years ago
Reply to  AnthonyHaddad

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
Tron
4 years ago
Reply to  Gon Her

How u get rid of volley error null pls mention here

Sergio Cordova
Sergio Cordova
4 years ago
Reply to  Gon Her

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

ChristosP
ChristosP
4 years ago
Reply to  Gon Her

Firewall rules. Permit traffic to the web port.

justDisqus
justDisqus
4 years ago
Reply to  Gon Her

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

missQ
missQ
4 years ago
Reply to  Gon Her

turn off your firewall and try again

AnthonyHaddad
AnthonyHaddad
4 years ago

hello Ravi,

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

Gon Her
Gon Her
4 years ago

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
Bala Murali
4 years ago

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

Ravi Tamada
4 years ago
Reply to  Bala Murali

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

Geraldo K Fillipus
Geraldo K Fillipus
4 years ago
Reply to  Ravi Tamada

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
David Batista Taboada
4 years ago

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
manasseh
4 years ago

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
Sahar Fathy
4 years ago
Reply to  manasseh

same here did you fix it

Tron
Tron
4 years ago
Reply to  manasseh

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

Milan Gajera
Milan Gajera
4 years ago

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
Myscrap New
4 years ago

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

ArtyMorty
ArtyMorty
4 years ago

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
Fatah Zull
4 years ago

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

Ji
Ji
4 years ago

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

Gaurav Singh
Gaurav Singh
4 years ago

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
ananymous
4 years ago
Reply to  Gaurav Singh

same here!

225
0
Would love your thoughts, please comment.x
()
x