Push Notifications Using Google Cloud Messaging (GCM)

Posted on

We had to implement in some of our android apps the ability to send push notifications in order to send a message or a notification of a new event to the devices, request data synchronization, etc. In this case we will be showing a scenario using PHP and MySQL on the backend, which will serve downstream messages through HTTP to our app.
As you might know the worflow in which Google Cloud Message works is as follows:

  1. First android device sends sender id and application id to the GCM server for registration.
  2. Upon successful registration GCM server issues registration id to android device
  3. After receiving registration id, device will send registration id to our server
  4. Our server will store registration id in the database for later usage.
  5. After this, whenever a push notification is required, our server sends a message to GCM server along with the device registration id
  6. GCM server will deliver that message to the right mobile device using that registration id.

In order to register your project into the Google Cloud Messaging you will need to access to Google APIs Console page

The project ID you get will then be the one used as the sender id in your android app. You must then go and click on Services on the left pane and turn on Google Cloud Messaging for Android.
Finally, click on API Access and note down the API Key, as this API key will be used when sending requests to GCM server.
Ok now let’s jump to the code and see how it is done!

Components

In order to get this to work we’ll need to create a few components in our android app.

GcmBroadcastReceiver

This is the Receiver that receives notifications from GCM servers and starts a service to process the message in a background thread. It basically forwards the intent to the background service, in our case will be using an IntentService. If you noticed, we are using a WakefulBroadcastReceiver which is a helper class for implementing a Receiver that gets a device wakeup event (holding a wake lock) and starts a service to do the work, all this ensuring that the device doesn’t go back to sleep while the service is setup and starts. The service then notifies the receiver to release the wake lock once it’s work is done.

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK |
                PowerManager.ACQUIRE_CAUSES_WAKEUP |
                PowerManager.ON_AFTER_RELEASE, "WakeLock");
        wakeLock.acquire();
        ComponentName comp = new ComponentName(context.getPackageName(),
                GcmIntentService.class.getName());
        // Start the service, keeping the device awake while it is launching.
        startWakefulService(context, (intent.setComponent(comp)));
        setResultCode(Activity.RESULT_OK);
        wakeLock.release();
    }
}

We then have to define the receiver in the manifest, providing the right intent filter to be able to respond to GCM messages. Consequently, we set the intent filter action to be com.google.android.c2dm.intent.Receive

<receiver
       android:name=".network.gcm.GcmBroadcastReceiver"
       android:permission="com.google.android.c2dm.permission.SEND" >
     <intent-filter>
          <action android:name="com.google.android.c2dm.intent.RECEIVE" />
          <category android:name="com.infuy.example.android.gcm" />
     </intent-filter>
</receiver>

GcmIntentService

This is the IntentService that handles the intent that comes from GcmBroadcastReceiver and sends a notification with the message content once it’s done

public class GcmIntentService extends IntentService {
    private static final String TAG = GcmIntentService.class.getName();
    public static final int NOTIFICATION_ID = 1;
    public GcmIntentService() {
        super(GcmIntentService.class.getSimpleName());
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle extras = intent.getExtras();
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
        String messageType = gcm.getMessageType(intent);
        if (!extras.isEmpty()) {  // has effect of unparcelling Bundle
            switch (messageType) {
                case GoogleCloudMessaging.
                        MESSAGE_TYPE_SEND_ERROR:
                    sendNotification("Send error: " + extras.toString());
                    break;
                case GoogleCloudMessaging.
                        MESSAGE_TYPE_DELETED:
                    sendNotification("Deleted messages on server: " +
                            extras.toString());
                    // If it's a regular GCM message, do some work.
                    break;
                case GoogleCloudMessaging.
                        MESSAGE_TYPE_MESSAGE:
                    // Post notification of received message.
                    sendNotification(extras.getString("message"));
                    Log.i(TAG, "Received: " + extras.toString());
                    break;
            }
        }
        // Release the wake lock provided by the WakefulBroadcastReceiver.
        GcmBroadcastReceiver.completeWakefulIntent(intent);
    }
    // Put the message into a notification and post it.
    private void sendNotification(String msg) {
        NotificationManager mNotificationManager = (NotificationManager)
                this.getSystemService(Context.NOTIFICATION_SERVICE);
        Intent notificationIntent = new Intent(this, NotificationMessageActivity.class);
        notificationIntent.putExtra(NotificationMessageActivity.NOTIFICATION_MESSAGE, msg);
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        // Adds the back stack
        stackBuilder.addParentStack(NotificationMessageActivity.class);
        // Adds the Intent to the top of the stack
        stackBuilder.addNextIntent(notificationIntent);
        // Gets a PendingIntent containing the entire back stack
        PendingIntent contentIntent =
                stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle(getString(R.string.notification_title))
                .setStyle(new NotificationCompat.BigTextStyle()
                        .bigText(msg))
                .setContentText(msg)
                .setAutoCancel(true);
        mBuilder.setContentIntent(contentIntent);
        Notification notification = mBuilder.build();
        notification.defaults |= Notification.DEFAULT_VIBRATE;
        notification.defaults |= Notification.DEFAULT_SOUND;
        mNotificationManager.notify(NOTIFICATION_ID, notification);
    }
}

As well as we did with the receiver, we need to define the IntentService in the application manifest in order to have it available in our app.

<service android:name=".network.gcm.GcmIntentService" />

Permissions

Your application must request some permissions to the Android system.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<permission
 android:name="com.example.gcm.permission.C2D_MESSAGE"
 android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />

Example code

The following method will check if the Google Play Services APK is installed and available on the device. If not, it will attempt to fix the error showing an error dialog which will lead the user to Google Play app so they can download and install the required APK.

private boolean checkPlayServices() {

    int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getActivity());

    if (resultCode != ConnectionResult.SUCCESS) {

        if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
            GooglePlayServicesUtil.getErrorDialog(resultCode, getActivity(),
                    PLAY_SERVICES_RESOLUTION_REQUEST).show();

        } else {

            Log.i(TAG, "This device is not supported.");

            getActivity().finish();

        }

        return false;

    }

    return true;

}

The following method registers the application with GCM servers asynchronously and stores the registration ID and app versionCode in the application’s
 shared preferences.


private void registerInBackground() {


new AsyncTask<Void, Void, String>() {

        @Override

        protected String doInBackground(Void... params) {

            String msg;

            try {

                if (mGcm == null) {

                    mGcm = GoogleCloudMessaging.getInstance(getActivity());

                }

                mRegId = mGcm.register(getString(R.string.gcm_sender_id));

                msg = "Device registered, registration ID=" + mRegId;




                storeRegistrationId(getActivity(), mRegId);

            } catch (IOException ex) {

                msg = "Error :" + ex.getMessage();

            }


     return msg;

}


@Override

protected void onPostExecute(String msg) {

            Log.d(TAG, msg);

        }

    }.execute();

}

Once you have the Registration Id from GCM servers, you need to send it to your backed server which will include the id when triggering a notifcation. The following example shows a Volley request, sending the registration id to our server. Further reading about Volley here

JsonRequest request = new JsonRequest
        (Request.Method.POST, RegistrationService.URL, new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                // handle server response ...
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // handle server error response ...
            }
        }){
    @Override
    protected Map<String, String> getParams() {
        Map<String, String> params = new HashMap<>();
        params.put("gcm_regid", mRegId); // mRegId is the registration Id obtained from gcm.register()
        return params;
    }
};
request.setTag(TAG);
ServicesHandler.getInstance(getActivity()).addToRequestQueue(request);

On the backend, you’ll need to have a place to store this registration Id (usually in a table)  and associate it with your users in order to attach the Id to the push notification requests you send to GCM servers.
This is the snippet of code on the php backend that sends the notification to the device through the GCM servers.

/**
     * Sending Push Notification
     */
    public function sendnotificationAction() {
        ...........
        // Get the user_id
        $register_id = $user->gcm_regid;
        $message = ‘This is a notification to send to the user’;
        // Set POST variables
        define("GOOGLE_API_KEY", “XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX”);
        $url = 'https://android.googleapis.com/gcm/send';
        $registration_ids = array($register_id);
        $message_json = array("message" => $message);
        $fields = array("data" => $message_json, 'registration_ids' => $registration_ids);
        $headers = array(
            'Authorization: key=' . GOOGLE_API_KEY,
            'Content-Type: application/json'
        );
        // Open connection
        $ch = curl_init();
        // Set the url, number of POST vars, POST data
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
        // Execute post
        $result = curl_exec($ch);
        if ($result === FALSE) {
            die('Curl failed: ' . curl_error($ch));
        }
        // Close connection
        curl_close($ch);
        return true;
    }

And that’s pretty much it. We encourage you to try introducing push notifications in your app, as it not only introduces a mechanism to send push notifications from your backend and get them displayed them in your android status bar, but it also allows you to have your server begin a communication with your app. This is useful to prevent your app from having to poll your server for some new event or data, which means avoiding the cell radio draining your battery and have your app use fresh data instantly. Moreover, you can have your server trigger periodic sync updates at any given time or when some new data is available and eligible to be updated on your client apps.

Posted in Android, Mobile, Software Development, TechnologiesTagged , , , , , ,

infuy
linkedin logo
twitter logo
instagram logo
facebook logo
By infuy
Infuy is an international technology leader company specialized in outsourcing services in the latest technologies. Blockchain (smart contracts, dApps and NFT marketplaces), full-stack, mobile development, and team augmentation are some of the areas where Infuy specializes in. As a software development company based in Uruguay with offices in the US, Infuy is always looking for the next big challenge, and the next big partner. As a fast-growing company, Infuy is always looking for talented and proactive people, who live up to the challenge and are as passionate as their team is about their mission to provide the best solutions to their partners, with the latest technologies, to join the team. For more information about the company and its services, please visit https://www.infuy.com/.