Android Push Notification Using GCM (client and server side implementation)
Applicable To: Android Studio
Updated: Friday, 01. November 2019 04:41 PM UTC
GCM (Google Cloud Messaging) documentation says:
Send data from your server to your users' devices, and receive messages from devices on the same connection. The GCM service handles all aspects of queueing of messages and delivery to client applications running on target devices, and it is completely free.
To get this service from Google, however, you will need to do certain things:
This tutorial will cover the following criteria/features regarding GCM Push Notification:
Let's Start
You can skip this step if you want to use the project id that was saved from last step. But Google recommends using a configuration file instead and I will be using the configuration file method in my code, though I will give a hint on how you can use the project id instead of this configuration file.
Create an empty project in Android studio. Use whatever template you like (I have used a navigation drawer template). Copy the google-services.json file to the app directory of your project.
classpath 'com.google.gms:google-services:2.0.0-beta2'
in the dependencies section of your project level build.gradle file.compile 'com.google.android.gms:play-services-gcm:8.4.0'
in the dependencies section of you app level build.gradle file.apply plugin: 'com.google.gms.google-services'
at the end of your app level build.gradle file.This is how my Gradle files look like:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0-beta2'
classpath 'com.google.gms:google-services:2.0.0-beta2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion '23.0.2'
defaultConfig {
applicationId "org.neurobin.aapps.pushnotificationgcmclient"
minSdkVersion 9
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:design:23.1.1'
compile 'com.google.android.gms:play-services-gcm:8.4.0'
compile 'com.google.android.gms:play-services-ads:8.4.0'
}
apply plugin: 'com.google.gms.google-services'
#Wed Oct 21 11:34:03 PDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
android:permission="com.google.android.c2dm.permission.SEND"
This is how my manifest file looks like:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.neurobin.aapps.pushnotificationgcmclient">
<permission
android:name="org.neurobin.aapps.pushnotificationgcmclient.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="org.neurobin.aapps.pushnotificationgcmclient.permission.C2D_MESSAGE" />
<!-- [START gcm_permission] -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- [END gcm_permission] -->
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_short_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".PushNotificationMainActivity"
android:label="@string/app_short_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- [START gcm_receiver] -->
<receiver
android:name="com.google.android.gms.gcm.GcmReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="org.neurobin.aapps.pushnotificationgcmclient" />
</intent-filter>
</receiver>
<!-- [END gcm_receiver] -->
<!-- [START gcm_listener] -->
<service
android:name=".MyGcmListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
<!-- [END gcm_listener] -->
<!-- [START instanceId_listener] -->
<service
android:name=".MyInstanceIDListenerService"
android:exported="false">
<intent-filter>
<action android:name="com.google.android.gms.iid.InstanceID" />
</intent-filter>
</service>
<!-- [END instanceId_listener] -->
<service
android:name=".MyGCMRegistrationIntentService"
android:exported="false"></service>
<activity
android:name=".ScrollingActivity"
android:label="@string/app_short_name"
android:theme="@style/AppTheme.NoActionBar">
</activity>
</application>
</manifest>
Let's see an overview of the classes and methods used throughout this project. It will help better understand what's actually happening under the hood.
putExtra()
method.BroadcastReceiver
to handle messages from the intent service MyGCMRegistrationIntentService
, and a GCM related method startRegistrationService()
which starts the MyGCMRegistrationIntentService
to register/un-register the device with GCM and your server.
checkPlayServices(Activity activity)
: Check play services API availability and install/update if necessary. Defined in GCMCommonUtils class.onMessageReceived(String from, Bundle data)
: Override method in MyGcmListenerService. Handles messages received through push notification.sendNotification(String message, String type)
: Generates notification on the device with the message and triggers an event if clicked from the notification area in device according to the type specified.onHandleIntent(Intent intent)
: Override method in MyGCMRegistrationIntentService, handles registration intent, registers or unregisters with GCM and server.registerWithServer(String token, InstanceID instanceID)
: Defined in MyGCMRegistrationIntentService. Sends the token and the instanceID (string) to server in order to register with the server. It employs an exponential backoff mechanism to retry in case of failure.unregisterFromServer(final String regId)
: Defined in MyGCMRegistrationIntentService. Sends the token/regid to server and unregisters the device for the server. It employs an exponential backoff mechanism to retry in case of failure.post(String endpoint, Map<String, String> params)
: Defined in MyGCMRegistrationIntentService. It triggers an HTTP post request.startRegistrationService(boolean reg, boolean tkr)
: Defined in PushNotificationMainActivity. If reg
is true
, it tries to register, if false
it tries to un-register. If tkr
is true
, a fresh token is retrieved, otherwise old token from shared preferences is used.package org.neurobin.aapps.pushnotificationgcmclient;
import android.app.Activity;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
/**
* Created by jahid on 16/02/16.
*/
public class GCMCommonUtils {
private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
// put your server registration url here, must end with a /
public static final String SERVER_URL = "https://neurobin.org/api/android/gcm/gcmsender/";
public static String notificationType[] = {"default", "type1"};
/**
* Tag used on log messages.
*/
static final String TAG = "androidpushnotification";
public static final String DISPLAY_MESSAGE_ACTION =
"org.neurobin.aapps.pushnotificationclient";
static final String EXTRA_MESSAGE = "message";
/**
* Check the device to make sure it has the Google Play Services APK. If
* it doesn't, display a dialog that allows users to download the APK from
* the Google Play Store or enable it in the device's system settings.
*/
public static boolean checkPlayServices(Activity activity) {
GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
int resultCode = apiAvailability.isGooglePlayServicesAvailable(activity);
if (resultCode != ConnectionResult.SUCCESS) {
if (apiAvailability.isUserResolvableError(resultCode)) {
apiAvailability.getErrorDialog(activity, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST)
.show();
} else {
Log.i(TAG, "This device is not supported.");
activity.finish();
}
return false;
}
return true;
}
}
Change the SERVER_URL to your actual server url. Be sure to end the SERVER_URL with a /. Note the notificationType
array. In general case, you will only be using the default type. You can change these types or add more, but in that case you will
need to change the server side code (index.php) to match these types and also in sendNotification()
method inside
MyGCMListenerService
class.
package org.neurobin.aapps.pushnotificationgcmclient;
/**
* Created by jahid on 16/02/16.
*/
public class GCMSharedPreferences {
public static final String SENT_TOKEN_TO_SERVER = "SENT_TOKEN_TO_SERVER";
public static final String REG_SUCCESS = "REG_SUCCESS";
public static final String SENT_UNREG_REQUEST_TO_SERVER = "SENT_UNREG_REQUEST_TO_SERVER";
public static final String GOT_TOKEN_FROM_GCM = "GOT_TOKEN_FROM_GCM";
public static final String REGISTRATION_COMPLETE = "REGISTRATION_COMPLETE";
public static final String REG_ID = "";
}
package org.neurobin.aapps.pushnotificationgcmclient;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.android.gms.gcm.GcmListenerService;
/**
* Created by jahid on 16/02/16.
*/
public class MyGcmListenerService extends GcmListenerService {
private static final String TAG = "MyGcmListenerService";
/**
* Called when message is received.
*
* @param from SenderID of the sender.
* @param data Data bundle containing message data as key/value pairs.
* For Set of keys use data.keySet().
*/
// [START receive_message]
@Override
public void onMessageReceived(String from, Bundle data) {
String message = "";
String type = "";
Log.d("data: ", data.toString());
for (String not_type : GCMCommonUtils.notificationType) {
if (data.get(not_type) != null) {
message = data.getString(not_type);
type = not_type;
}
}
Log.d(TAG, "From: " + from);
Log.d(TAG, "Message: " + message);
sendNotification(message, type);
// [END_EXCLUDE]
}
// [END receive_message]
/**
* Create and show a simple notification containing the received GCM message.
*
* @param message GCM message received.
*/
private void sendNotification(String message, String type) {
Intent intent = new Intent(this, PushNotificationMainActivity.class);
if (type.equalsIgnoreCase("default")) {
//nothing will be changed.
} else if (type.equalsIgnoreCase("type1")) {
//define intent to do some other tasks than running the main activity
intent = new Intent(this,ScrollingActivity.class);
} else if (type.equalsIgnoreCase("type2")) {
//define intent to do some other tasks than running the main activity
}
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(getString(R.string.app_short_name))
.setContentText(message)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
//notificationBuilder.setDefaults(Notification.DEFAULT_VIBRATE);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
See the if...else
block inside sendNotification()
method on how you can handle multiple types of
notifications and trigger different events for different types of notifications. Note that notification type does not refer to GCM topics
, they are entirely two different things.
package org.neurobin.aapps.pushnotificationgcmclient;
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.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
/**
* Created by jahid on 16/02/16.
*/
public class MyGCMRegistrationIntentService extends IntentService {
private static final String TAG = "RegIntentService";
private static final String[] TOPICS = {"global"};
private static final int MAX_ATTEMPTS = 10;
private static final int BACKOFF_MILLI_SECONDS = 500;
private static final Random random = new Random();
SharedPreferences sharedPreferences;
public MyGCMRegistrationIntentService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
Intent registrationComplete = new Intent(GCMSharedPreferences.REGISTRATION_COMPLETE);
registrationComplete.putExtra("prefix", "");
String token = sharedPreferences.getString(GCMSharedPreferences.REG_ID, "");
try {
// [START register_for_gcm]
// Initially this call goes out to the network to retrieve the token, subsequent calls
// are local.
// R.string.gcm_defaultSenderId (the Sender ID) is typically derived from google-services.json.
// See https://developers.google.com/cloud-messaging/android/start for details on this file.
// [START get_token]
InstanceID instanceID = InstanceID.getInstance(this);
if (!intent.getExtras().getBoolean("register")) {
//client wants to un-register
//instanceID.deleteInstanceID(); //This will delete the id i.e full un-register
instanceID.deleteToken(getString(R.string.gcm_defaultSenderId),
GoogleCloudMessaging.INSTANCE_ID_SCOPE); //This will delete the token not the ID (partial)
//delete from shared preferences
//You would generally delete selectively
//As this project is only about GCM notification, we can clear the sharedpreferences altogether
sharedPreferences.edit().clear().apply();
//un-register from server
unregisterFromServer(token);
} else {
if (!intent.getExtras().getBoolean("tokenRefreshed") && !token.equals("")) {
//No need of retrieving token from GCM
registrationComplete.putExtra("prefix", "Old Token:\n");
} else {
//retrieve a fresh token from GCM, it may or may not be the same as previous
token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
registrationComplete.putExtra("prefix", "Fresh Token:\n");
}
// [END get_token]
Log.i(TAG, "GCM Registration Token: " + token);
sharedPreferences.edit().putString(GCMSharedPreferences.REG_ID, token).apply();
sharedPreferences.edit().putBoolean(GCMSharedPreferences.REG_SUCCESS, true).apply();
// send registration request to server
registerWithServer(token, instanceID);
// Subscribe to topic channels
//subscribeTopics(token);
// [END register_for_gcm]
}
} catch (Exception e) {
sharedPreferences.edit().putBoolean(GCMSharedPreferences.REG_SUCCESS, false).apply();
Log.d(TAG, "Registration service failed.", e);
}
// Notify UI that task has completed, so the progress indicator can be hidden.
if (intent.getExtras().getBoolean("register"))
registrationComplete.putExtra("register", true);
else
registrationComplete.putExtra("register", false);
LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
}
/**
* Persist registration to third-party servers.
* <p/>
* Modify this method to associate the user's GCM registration token with any server-side account
* maintained by your application.
*
* @param token The new token.
*/
private void registerWithServer(String token, InstanceID instanceID) {
// Log.i(TAG, "registering device (regId = " + regId + ")");
String serverUrl = GCMCommonUtils.SERVER_URL + "register.php";
Map<String, String> params = new HashMap<String, String>();
params.put("regId", token);
params.put("instanceId",instanceID.toString());
// params.put("name", name);
// params.put("email", email);
long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000);
// Once GCM returns a registration id, we need to register on our server
// As the server might be down, we will retry it a couple of
// times.
for (int i = 1; i <= MAX_ATTEMPTS; i++) {
// Log.d(TAG, "Attempt #" + i + " to register");
try {
post(serverUrl, params);
// You should store a boolean that indicates whether the generated token has been
// sent to your server. If the boolean is false, send the token to your server,
// otherwise your server should have already received the token.
sharedPreferences.edit().putBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, true).apply();
return;
} catch (IOException e) {
Toast.makeText(this, "Failed to register...", Toast.LENGTH_SHORT).show();
sharedPreferences.edit().putBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, false).apply();
// Here we are simplifying and retrying on any error; in a real
// application, it should retry only on unrecoverable errors
// (like HTTP error code 503).
// Log.e(TAG, "Failed to register on attempt " + i + ":" + e);
if (i == MAX_ATTEMPTS) {
break;
}
try {
// Log.d(TAG, "Sleeping for " + backoff + " ms before retry");
Thread.sleep(backoff);
} catch (InterruptedException e1) {
// Activity finished before we complete - exit.
// Log.d(TAG, "Thread interrupted: abort remaining retries!");
Thread.currentThread().interrupt();
return;
}
// increase backoff exponentially
backoff *= 2;
}
}
}
/**
* Unregister this account/device pair within the server.
*/
private void unregisterFromServer(final String regId) {
// Log.i(TAG, "registering device (regId = " + regId + ")");
String serverUrl = GCMCommonUtils.SERVER_URL + "unregister.php";
Map<String, String> params = new HashMap<String, String>();
params.put("regId", regId);
long backoff = BACKOFF_MILLI_SECONDS + random.nextInt(1000);
// Once GCM returns a registration id, we need to unregister on our server
// As the server might be down, we will retry it a couple
// times.
for (int i = 1; i <= MAX_ATTEMPTS; i++) {
try {
post(serverUrl, params);
sharedPreferences.edit().putBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, true).apply();
return;
} catch (IOException e) {
Toast.makeText(this, "Failed to un-register...", Toast.LENGTH_SHORT).show();
sharedPreferences.edit().putBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, false).apply();
// Here we are simplifying and retrying on any error; in a real
// application, it should retry only on unrecoverable errors
// (like HTTP error code 503).
// Log.e(TAG, "Failed to register on attempt " + i + ":" + e);
if (i == MAX_ATTEMPTS) {
break;
}
try {
// Log.d(TAG, "Sleeping for " + backoff + " ms before retry");
Thread.sleep(backoff);
} catch (InterruptedException e1) {
// Activity finished before we complete - exit.
// Log.d(TAG, "Thread interrupted: abort remaining retries!");
Thread.currentThread().interrupt();
return;
}
// increase backoff exponentially
backoff *= 2;
}
}
// String message = context.getString(R.string.server_register_error,
// MAX_ATTEMPTS);
// CommonUtilities.displayMessage(context, message);
}
/**
* Issue a POST request to the server.
*
* @param endpoint POST address.
* @param params request parameters.
* @throws IOException propagated from POST.
*/
private static void post(String endpoint, Map<String, String> params)
throws IOException {
URL url;
try {
url = new URL(endpoint);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("invalid url: " + endpoint);
}
StringBuilder bodyBuilder = new StringBuilder();
Iterator<Map.Entry<String, String>> iterator = params.entrySet().iterator();
// constructs the POST body using the parameters
while (iterator.hasNext()) {
Map.Entry<String, String> param = iterator.next();
bodyBuilder.append(param.getKey()).append('=')
.append(param.getValue());
if (iterator.hasNext()) {
bodyBuilder.append('&');
}
}
String body = bodyBuilder.toString();
// Log.v(TAG, "Posting '" + body + "' to " + url);
byte[] bytes = body.getBytes();
HttpURLConnection conn = null;
try {
Log.e("URL", "> " + url);
conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setFixedLengthStreamingMode(bytes.length);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
// post the request
OutputStream out = conn.getOutputStream();
out.write(bytes);
out.close();
// handle the response
int status = conn.getResponseCode();
if (status != 200) {
throw new IOException("Post failed with error code " + status);
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
/**
* Subscribe to any GCM topics of interest, as defined by the TOPICS constant.
*
* @param token GCM token
* @throws IOException if unable to reach the GCM PubSub service
*/
// [START subscribe_topics]
/*private void subscribeTopics(String token) throws IOException {
GcmPubSub pubSub = GcmPubSub.getInstance(this);
for (String topic : TOPICS) {
pubSub.subscribe(token, "/topics/" + topic, null);
}
}*/
// [END subscribe_topics]
}
Notice the part token = instanceID.getToken(getString(R.string.gcm_defaultSenderId)
. The R.string.gcm_defaultSenderId
resource is automatically parsed from the google-services.json file. If you didn't include the configuration file then put
the sender id i.e the project id that you saved from Google developer console.
package org.neurobin.aapps.pushnotificationgcmclient;
import android.content.Intent;
import com.google.android.gms.iid.InstanceIDListenerService;
/**
* Created by jahid on 16/02/16.
*/
public class MyInstanceIDListenerService extends InstanceIDListenerService {
private static final String TAG = "MyInstanceIDLS";
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. This call is initiated by the
* InstanceID provider.
*/
// [START refresh_token]
@Override
public void onTokenRefresh() {
// Fetch updated Instance ID token and notify our app's server of any changes (if applicable).
Intent intent = new Intent(this, MyGCMRegistrationIntentService.class);
intent.putExtra("register",true); //register=true
intent.putExtra("tokenRefreshed",true); //tokenRefreshed = true
startService(intent);
}
// [END refresh_token]
}
package org.neurobin.aapps.pushnotificationgcmclient;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.design.widget.NavigationView;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
public class PushNotificationMainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, View.OnClickListener {
private static final String TAG = "MainActivity";
private BroadcastReceiver mRegistrationBroadcastReceiver;
private ProgressBar mRegistrationProgressBar;
private TextView mInformationTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gcm_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
Button rtbtn = (Button) findViewById(R.id.ret_btn);
Button unreg_btn = (Button) findViewById(R.id.unreg_btn);
rtbtn.setOnClickListener(this);
unreg_btn.setOnClickListener(this);
//GCM related part
mRegistrationProgressBar = (ProgressBar) findViewById(R.id.registrationProgressBar);
mRegistrationBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mRegistrationProgressBar.setVisibility(ProgressBar.GONE);
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context);
boolean sentToken = sharedPreferences
.getBoolean(GCMSharedPreferences.SENT_TOKEN_TO_SERVER, false);
String tkmsg = "\nRegistration ID from GCM: " +
sharedPreferences.getString(GCMSharedPreferences.REG_ID, "N/A");
tkmsg = getString(R.string.gcm_send_message) + tkmsg;
if (!intent.getExtras().getBoolean("register"))
tkmsg = getString(R.string.gcm_unregister_message);
tkmsg = intent.getStringExtra("prefix") + tkmsg;
if (sentToken) {
mInformationTextView.setText(tkmsg);
} else {
mInformationTextView.setText(getString(R.string.token_error_message) + tkmsg);
}
}
};
mInformationTextView = (TextView) findViewById(R.id.informationTextView);
startRegistrationService(true, false);
//GCM related part end
}
@Override
protected void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
new IntentFilter(GCMSharedPreferences.REGISTRATION_COMPLETE));
}
@Override
protected void onPause() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);
super.onPause();
}
@Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.gcm_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_home) {
// Handle the camera action
} else if (id == R.id.nav_gallery) {
Intent intent = new Intent(this, ScrollingActivity.class);
startActivity(intent);
} else if (id == R.id.nav_slideshow) {
} else if (id == R.id.nav_manage) {
} else if (id == R.id.nav_share) {
} else if (id == R.id.nav_send) {
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.ret_btn) {
startRegistrationService(true, true);
}
if (view.getId() == R.id.unreg_btn) {
//try to unregister
startRegistrationService(false, false);
}
}
public void startRegistrationService(boolean reg, boolean tkr) {
if (GCMCommonUtils.checkPlayServices(this)) {
mRegistrationProgressBar = (ProgressBar) findViewById(R.id.registrationProgressBar);
mRegistrationProgressBar.setVisibility(View.VISIBLE);
TextView tv = (TextView) findViewById(R.id.informationTextView);
if (reg) tv.setText(R.string.registering_message);
else tv.setText(R.string.unregistering_message);
Toast.makeText(this, "Background service started...", Toast.LENGTH_LONG).show();
// Start IntentService to register this application with GCM.
Intent intent = new Intent(this, MyGCMRegistrationIntentService.class);
intent.putExtra("register", reg);
intent.putExtra("tokenRefreshed", tkr);
startService(intent);
}
}
}
This is the main activity. It may differ in your case depending on how you have set up the project from the beginning. Use whatever activity and xml resources you like but be sure to include a TextView a ProgressBar and two Buttons if you don't want to change the codes for those resources.
The only GCM related codes in this activity are the codes that are marked with//GCM related part .. //GCM related part end
and thestartRegistrationService()
method. Also note that the activity implements theView.OnClickListener
class to handle onclick event on the buttons and theonClick()
override method calls the GCM related methodstartRegistrationService()
on demand.
package org.neurobin.aapps.pushnotificationgcmclient;
import android.os.Bundle;
import android.app.Activity;
public class ScrollingActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrolling);
}
}
XML codes are dependent on how you have setup the project from the beginning. I have used a navigation drawer template on this project and thus there are lots of XML codes involved, but you may not need all those. I am including some vital parts (that I have used) of the project:
The TextView, ProgressBar and Buttons:
<TextView android:text="@string/registering_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/informationTextView"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/registrationProgressBar" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="REFRESH"
android:id="@+id/ret_btn"
android:background="#4ec9ac"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:enabled="true"
android:clickable="true"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:textStyle="bold" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UNREG"
android:id="@+id/unreg_btn"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:background="#B90000"
android:alpha=".9"
android:clickable="true"
android:enabled="true"
android:hint="Unsubscribe"
android:textColor="#fff"
android:textStyle="bold"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp" />
String resources:
<string name="app_name">Push Notification GCM Client</string>
<string name="app_short_name">PNGCM Demo</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="action_settings">Settings</string>
<string name="server_registered">Successfully registered on server</string>
<string name="gcm_send_message">Token retrieved and sent to server! You can now use gcmsender to
send downstream messages to this app.</string>
<string name="gcm_unregister_message">Token unsubscribed from GCM and notification server. You will no longer
get any notification.</string>
<string name="registering_message">Generating InstanceID token...</string>
<string name="unregistering_message">Un-registering Notification Service...</string>
<string name="token_error_message">An error occurred while either fetching the InstanceID token,
sending the fetched token to the server. Please refresh.</string>
If you are still having problems with XML and other resources, feel free to download the project from Github. Links at top.
That's it. You should have now successfully created the client app. if you are having problems with configuring or resource dependencies, then download my project from Github.Also you don't need to change anything at all in the downloaded project and don't need to have a server to run the code. The example code uses my test server to register or unregister. You can view my test admin panel here, I am not allowing sending push notification though.I no longer provide the test server, please use localhost for testing.
Now, let's do some server side coding. To test this you need an working server. Either WAMP, XAMP, LAMP or a Live server running PHP will do. Make sure that the server supports php curl module and MySqli.
If you want to run it in a localhost, then you will need to use the client app only in emulator; if you want to test the client app with real device, you may need to connect your device with the same connection as your localhost or expose your localhost to Internet so that your device can send post requests to localhost.
For example, If your device and localhost are in the same wifi, there's nothing to worry about.
My recommendation is to test it in a live server with a real device.
You can easily create a database with phpmyadmin. If you have set up your server correctly and installed phpmyadmin, then in localhost the url http://localhost/phpmyadmin should open it for you. How to set up the server or install phpmyadmin is out of the scope of this tutorial. If you don't know how to do that, you will need to search a bit and learn that first before you go any further, as I don't have any related tutorial on my site yet.
If you are on a live server you can open phpmyadmin from cpanel or whatever control panel you have.
If there are other means to create MySql database, database user, and running SQL command, then that will do too.
CREATE TABLE IF NOT EXISTS `gcm_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`gcm_instance_id` text,
`gcm_regid` text,
`name` varchar(50) NOT NULL,
`email` varchar(255) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
It will create a table named gcm_users with 6 columns.
redirect($url)
: Redirects to the specified url.storeUser($gcm_regid, $instanceId, $name, $email)
: Member function of DB_Functions_GCM class. Stores registration id in database along with other info.checkUserById($id)
: Member function of DB_Functions_GCM class. Checks if an existing user already exists in database with the same registration id.deleteUserById($id)
: Member function of DB_Functions_GCM class. Deletes a user from database according to the registration id (unsubscribe).sendPushNotification($registration_ids, $message)
: Sends push notification $message
to all ids in the $registration_ids
array.<?php
/**
* Database config variables
*/
define("DB_HOST", "localhost");
define("DB_USER", ""); //fill with value
define("DB_PASSWORD", ""); //fill with value
define("DB_DATABASE", ""); //fill with value
/*
* Google API Key
*/
define("GOOGLE_API_KEY", ""); // Place your Google API Key
//define('GOOGLE_API_URL','https://android.googleapis.com/gcm/send'); //deprecated
define("GOOGLE_API_URL","https://gcm-http.googleapis.com/gcm/send");
define("BASE_URL", "https://neurobin.org/"); //optional
define("PWD", "./");
define("PWP", "https://neurobin.org/api/android/gcm/gcm-server-demo/"); //optional
?>
Edit this file according to how you have set up things so far. DB_DATABASE
is
the name of the database (gcm). You don't need to change the DB_HOST
,
GOOGLE_API_URL
and PWD
part.
<?php
function redirect($url) {
if (!headers_sent()) {
header('Location: ' . $url);
exit ;
} else {
echo '<script type="text/javascript">';
echo 'window.location.href="' . $url . '";';
echo '</script>';
echo '<noscript>';
echo '<meta http-equiv="refresh" content="0;url=' . $url . '" />';
echo '</noscript>';
exit ;
}
}
?>
You can use this file as it is. No modifications needed.
<?php
require_once 'config.php';
class DB_Functions_GCM {
//put your code here
// constructor
function __construct() {
// connecting to database
$GLOBALS['mysqli_connection'] = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE) or die("Mysqli Error " . mysqli_error($GLOBALS['mysqli_connection']));
}
// destructor
function __destruct() {
}
public function connectDefaultDatabase() {
$GLOBALS['mysqli_connection'] = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE) or die("Mysqli Error " . mysqli_error($GLOBALS['mysqli_connection']));
return $GLOBALS['mysqli_connection'];
}
public function selectDatabase($db) {
mysqli_select_db($GLOBALS['mysqli_connection'], $db);
}
public function closeDatabase() {
mysqli_close($GLOBALS['mysqli_connection']);
}
public function connectNewDatabase($host, $user, $password, $dbname = "") {
closeDatabase();
if ($dbname != "" && $dbname != null) {
$GLOBALS['mysqli_connection'] = mysqli_connect($host, $user, $password, $dbname) or die("Mysqli Error " . mysqli_error($GLOBALS['mysqli_connection']));
} else {$GLOBALS['mysqli_connection'] = mysqli_connect($host, $user, $password) or die("Mysqli Error " . mysqli_error($GLOBALS['mysqli_connection']));
}
return $GLOBALS['mysqli_connection'];
}
/**
* Storing new user
* returns user details
*/
/**
* Get user by email and password
*/
public function getUserByEmail($email) {
$result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT * FROM gcm_users WHERE email = '$email' LIMIT 1");
return $result;
}
public function getUserByName($username) {
$result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT * FROM gcm_users WHERE name = '$username' LIMIT 1");
return $result;
}
public function getUserById($id) {
$result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT * FROM gcm_users WHERE id = '$id' LIMIT 1");
return $result;
}
/**
* Getting all users
*/
public function getAllUsers() {
$result = mysqli_query($GLOBALS['mysqli_connection'], "select * FROM gcm_users");
return $result;
}
/**
* Check user exists or not
*/
public function checkUserById($id) {
$result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT gcm_regid from gcm_users WHERE gcm_regid = '$id'");
$no_of_rows = mysqli_num_rows($result);
if ($no_of_rows > 0) {
return true;
} else {
return false;
}
}
public function deleteUserById($id) {
$result = mysqli_query($GLOBALS['mysqli_connection'], "DELETE FROM gcm_users WHERE gcm_regid = '$id'");
if ($result) {
return true;
} else {
return false;
}
}
public function storeUser($gcm_regid, $instanceId, $name, $email) {
echo "$gcm_regid";
// insert user into database
$result = mysqli_query($GLOBALS['mysqli_connection'], "INSERT INTO gcm_users(name, email, gcm_instance_id, gcm_regid, created_at) VALUES('$name', '$email', '$instanceId', '$gcm_regid', NOW())");
// check for successful store
if ($result) {
// get user details
$id = mysqli_insert_id($GLOBALS['mysqli_connection']);
// last inserted id
$result = mysqli_query($GLOBALS['mysqli_connection'], "SELECT * FROM gcm_users WHERE id = $id") or die("Error " . mysqli_error($GLOBALS['mysqli_connection']));
// return user details
if (mysqli_num_rows($result) > 0) {
return mysqli_fetch_array($result);
} else {
return false;
}
} else {
return false;
}
}
}
?>
You can use this file as it is. No modifications needed.
<?php
require_once 'db_functions.php';
$db = new DB_Functions_GCM();
require_once 'commonutils.php';
//////////////Do some validation First//////////////
$error = 0;
if (isset($_POST['message'])) {
$pushMessage = $_POST['message'];
if ($pushMessage == "" || $pushMessage == null) {echo "<br>Error: message can not be empty<br>";
$error++;
}
} else {echo "<br>Are you somehow trying to hack me?<br>Forget it buddy...<br>Close your computer and go to sleep...<br>";
$error++;
}
$nottype = (isset($_REQUEST['dropdown']) ? $_REQUEST['dropdown'] : "select");
if ($nottype == "select") {echo "<br>Error: Notification type must be selected.<br>";
$error++;
}
//////exit if error/////
if ($error != 0) {echo "<br><a href=\"" . constant("PWD") . "\">Go Back</a><br>";
exit ;
}
function sendPushNotification($registration_ids, $message) {
$url = GOOGLE_API_URL;
$fields = array('registration_ids' => $registration_ids, 'data' => $message, );
$headers = array('Authorization:key=' . GOOGLE_API_KEY, 'Content-Type: application/json');
echo json_encode($fields);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
$result = curl_exec($ch);
if ($result === false)
die('Curl failed ' . curl_error());
curl_close($ch);
return $result;
}
$pushStatus = '';
$query = "SELECT gcm_regid FROM gcm_users";
if ($query_run = mysqli_query($GLOBALS['mysqli_connection'], $query)) {
$gcmRegIds = array();
while ($query_row = mysqli_fetch_assoc($query_run)) {
array_push($gcmRegIds, $query_row['gcm_regid']);
}
}
if (isset($gcmRegIds) && isset($pushMessage)) {
$pushMessage = html_entity_decode($pushMessage);
$message = array($nottype => $pushMessage);
$regIdChunk = array_chunk($gcmRegIds, 1000);
foreach ($regIdChunk as $RegId) {
$pushStatus = sendPushNotification($RegId, $message);
}
redirect(PWD); //Comment this if you do not want to be redirected to previous page
} else {
echo "Unknown error occured, contact your Push Notification Service Provider";
exit ;
}
?>
You can use this file as it is. No modifications needed.
<html>
<head>
<title>Push Notification Admin Panel</title>
<style type="text/css">
html {
height: 100%;
font-size: 62.5%
}
body {
height: 100%;
background-color: #FFFFFF;
font: 1.4em Verdana, Arial, Helvetica, sans-serif;
}
/* ==================== Form style sheet ==================== */
/*div { align-content: center; padding-bottom: 30px; }*/
fieldset {
display: inline;
align-content: center;
border: 1px solid #095D92;
width: 50%
}
legend {
font-size: 1.1em;
background-color: #707070;
color: #FFFFFF;
font-weight: bold;
}
input.submit-button {
font: 1.4em Georgia, "Times New Roman", Times, serif;
letter-spacing: 1px;
display: block;
width: 25em;
height: 2em;
background-color: #d57079;
}
/* ==================== Form style sheet END ==================== */
</style>
</head>
<body>
<?php
require_once 'db_functions.php';
$db = new DB_Functions_GCM();
$users = $db -> getAllUsers();
if ($users != false)
$no_of_users = mysqli_num_rows($users);
else
$no_of_users = 0;
?>
<div><fieldset>
<h1>Neurobin Example</h1>
<h3>Push Notification Admin Panel</h3></fieldset></div><br></br>
<div><fieldset><legend>Current Registered users</legend>
<?php echo $no_of_users; ?></fieldset></div><br></br>
<form method = 'POST' action = 'gcm_main.php' enctype="multipart/form-data">
<div><fieldset><legend>Notification Type</legend>
<select name="dropdown">
<option value="default" selected="selected">Default</option>
<option value="type1">Type1</option>
</select></fieldset>
</div><br></br>
<div><fieldset><legend>Notification Message</legend>
<div>
<textarea rows = "3" name = "message" cols = "75" placeholder = "Type message here (keep as short as possible)" required></textarea>
</div></fieldset></div><br>
<div>
<input type = "submit" value = "Send Notification">
</div>
</form>
</body>
</html>
You can use this file as it is. No modifications needed. But you may want to modify it according to your choice and add as many eye candies as you want and make your admin panel look waaay more cool.
Note the part<select name="dropdown"> <option value="default" selected="selected">Default</option> <option value="type1">Type1</option> </select>
Notification types are defined here. You can change/add types here. For example, to add another type
type2
add the following block inside theselect
block:<option value="type2">Type2</option>
The value must match that of the type defined in
notificationType
array inGCMCommonUtils
class for the Client side implementation.
<?php
// response json
$json = array();
/**
* Registering a user device
* Store reg id in users table
*/
if (isset($_POST["regId"])) {
//set default values
$name = 'Anonymous';
$email = 'anonymous@anonymous.com';
$instanceId = '';
$gcm_regid = $_POST["regId"];
if(isset($_POST["name"])){$name=$_POST["name"];}
if(isset($_POST["email"])){$email=$_POST["email"];}
if(isset($_POST["instanceId"])){$instanceId=$_POST["instanceId"];}
// GCM Registration ID
// Store user details in db
include_once 'db_functions.php';
$db = new DB_Functions_GCM();
if ($db -> checkUserById($gcm_regid) == false && $gcm_regid != "" && $gcm_regid != null) {
$res = $db -> storeUser($gcm_regid,$instanceId,$name,$email);
if(!$res){echo 'Failed to write to database';}
} else {
echo 'Invalid regId';
}
} else {
echo 'regId not given';
}
?>
You can use this file as it is. No modifications needed.
<?php
// response json
$json = array();
/**
* Registering a user device
* Store reg id in users table
*/
if (isset($_POST["regId"])) {
$gcm_regid = $_POST["regId"];
// GCM Registration ID
// Store user details in db
include_once 'db_functions.php';
$db = new DB_Functions_GCM();
if ($db -> checkUserById($gcm_regid) == true) {
$res = $db -> deleteUserById($gcm_regid);
}
} else {
// user details missing
}
?>
You can use this file as it is. No modifications needed.
Put all those files under a directory named gcmsender. Thus your SERVER_URL (also your admin panel) will behttp://yourdomain.com/gcmsender/
and you will send registration requests tohttp://yourdomain.com/gcmsender/register.php
from your client app, unregistration requests tohttp://yourdomain.com/gcmsender/unregister.php
from your client app.yourdomain.com
may be localhost if you have done it in localhost.