Tutorial: How to Implement Push Notifications Using Google Cloud Messaging for Android — Part 1: Client App

by | Sep 18, 2015 | Computers




This tutorial will walk you through how to successfully send push notifications to an application using Google Cloud Messaging (GCM).

Part 1—Client App covers the setup needed for a client application to register with GCM, send that registration information to the app server, and handle notifications it receives.

The parts in this process are:

  1. Create a Google Developer Project
  2. Enable API
  3. Add Credentials
  4. Create Android Project
  5. Get Google-Services Configuration (JSON) File
  6. Set Android Build Dependencies
  7. Android Manifest Modification
  8. Create Service Classes
  9. Fill in MainActivity.java Class
  10. Add in Strings
  11. Add in Colors
  12. Add in Layout
  13. Next Steps

Step 1: Create a Google Developer Project

Go to console.developers.google.com/project.

Click Create Project. Give the project a name. This name can NEVER be changed. However, it’s just an identifier for the back-end code. The Project ID you see is random, and it is also only used for back-end code identification. You don’t have to worry about trying to change it to a specific value—the one it randomly generates is fine.

Step 2: Enable API

You will then be taken to the main Google Developer’s Console page. You should see the name of your project in the header toward the right, so you know you’re in the correct place.

Google Developer's Console

Under Mobile APIs, click on Google Cloud Messaging. (Sometimes there’s no Mobile APIs category and everything is listed together, in which case it’s called Google Cloud Messaging for Android.)

Google Developer's Console

Click Enable.

You will then get a message saying that you can’t use the API until you’ve created credentials. That’s the next step.

Google Cloud Messaging Credentials Notification

Step 3: Add Credentials

Click the button Go to Credentials, which will take you to a page that looks like this:

Create API Credentials Part 1

(You can get to the same page by going to the API Manager sidebar on the left and clicking on Credentials).

Under Where will you be calling the API from?, choose your platform. E.g. To make an Android app, choose Android.

Click What Credentials Do I Need? to move on to part 2:

Create API Credentials Part 2

Name your API key. At this point, you can also make your API more secure by clicking Add Package Name and Fingerprint”. Don’t do this now though unless you know what you’re doing. Just click Create API Key.

It will now show you your API key. Copy and save it to a safe location.

Step 4: Create Android Project

Create a new project in Android Studio (or Eclipse if that’s your preference; all instructions will reference Android Studio though). Open its AndroidManifext.xml and copy its package name (e.g. com.example.myapp) — you will need this for the next step.

Step 5: Get Google-Services Configuration (JSON) File

Make sure you’re signed into the same google account you used when you obtained your API key. Then go to the this link (part of the Google Developer’s Console) to obtain your Google Services JSON file.

You will see two fields. Click the drop-down for App Name and choose the corresponding project. Then in the space for Android Package Name, paste the package name of your Android project. Finally, click Continue to Choose and Configure Services.

Enable Google Services For Your App

If it’s not already selected, click Cloud Messaging.

Select Google Services

Then click Enable Google Cloud Messaging to enable it. It will then show your your Server API Key (different than your project’s API key) and Server ID. Save both to the same safe location you stored your other number.

Scroll all the way to the bottom and click Continue to Generate Configuration Files.

Continue to Generate Configuration Files

Click Download google-services.json to get the JSON file.

Download and Install Configuration

Copy the google-services.json to the app/ or mobile/ directory of your Android project (e.g. C:\User\MY_NAME\StudioProjects\MY_PROJECT_NAME\app).

Step 6: Set Android Build Dependencies

Open your project in Android Studio (if it’s not already open) and go to the Gradle Scripts directory and open build.gradle (Project: YOUR_PROJECT_NAME).

Inside the dependencies block, include

classpath 'com.google.gms:google-services:1.5.0
classpath 'com.google.gms:google-services:2.0.0-alpha3'

(Only add if it’s not already there. Keep whichever version is most recent.)

From the same directory, open build.gradle (Module:app). At the top of the file, put:

apply plugin: 'com.google.gms.google-services'

Inside the dependencies block, put

compile 'com.google.android.gms:play-services-gcm:8.3.0'

Make sure the version number of play-services is the latest version. You find all versions (including the latest) in
android-sdks\extras\google\m2repository\com\google\android\gms\play-services
(usually within C:\Users\YOUR_NAME.)

When you’re done, a pop-up should appear to let you sync your project with the gradle files.

Step 7: Android Manifest Modifications

Open your project’s AndroidManifest.xml file (located in Manifests).

Put the following permissions code between the <use-sdk> and <application> blocks:

<!-- Needs internet to connect to Google Services -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Google Services requires a Google account -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />

<!-- Keeps processor from sleeping when a message is received. -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

<!-- Permission to vibrate when receive a notification -->
<uses-permission android:name="android.permission.VIBRATE" />
	
<!-- Lets app receive data messages. -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

<!-- Creates a custom permission using "signature" so that only this app 
     can read the messages returned by GCM
         - YOUR_PACKAGE is your product's package name. E.g. com.example.test -->
<permission
     android:name="YOUR_PACKAGE.permission.C2D_MESSAGE"
     android:protectionLevel="signature" />
<uses-permission
     android:name="YOUR_PACKAGE.permission.C2D_MESSAGE" />

Put the following code in the <application> block, after the <activity> sub-block. This is the code for your broadcast receiver, which handles the messages sent from GCM. The SEND permission is held by Google Play services. This prevents other apps from invoking the broadcast receiver.

<receiver
    android:name="com.google.android.gms.gcm.GcmReceiver"
    android:permission="com.google.android.c2dm.permission.SEND" >
    <intent-filter>

        <!-- Receives the actual messages. -->
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
		
        <!-- Receives the registration id. -->
        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

        <category android:name="YOUR_PACKAGE" />

    </intent-filter>
</receiver>

Directly below the receiver code, define the service(s) you will call. Pay attention to the “.” at the front of the name. This means you’re going to put the Java class for that service in the same folder as your MainActivity class (which is located in the folder called YOUR_PACKAGE in the java folder).

<!-- Enables message handling (e.g.detecting different downstream message types, 
     determining upstream send status, and automatically displaying simple 
     notifications on the app’s behalf) -->

<service
    android:name=".MyGCMListenerService"
    android:exported="false" >
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
    </intent-filter>
</service>

<!-- Handles the creation and updating of registration tokens -->

<service
    android:name=".MyInstanceIDListenerService"
    android:exported="false" >
    <intent-filter>
        <action android:name="com.google.android.gms.iid.InstanceID" />
    </intent-filter>
</service>

<!-- To get the registration token -->

<service
    android:name=".RegistrationIntentService"
    android:exported="false" >
</service>

Expected Error: You will likely notice an error if you hover over the service name that says Error: Cannot resolve symbol “YOUR_SERVICE_NAME”. This is because we haven’t created the corresponding Java classes yet. We will do that in the next step.

Step 8: Create Service Classes

Create the following classes in the java directory (the same directory as MainActivity.java). The code for these classes comes from the Google-Services Sample Code.

Note: You need to include your package name at the top of every class (this is not shown in the following code).

MyGCMListenerService

/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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;

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 = data.getString("message");
        Log.d(TAG, "From: " + from);
        Log.d(TAG, "Message: " + message);

        if (from.startsWith("/topics/")) {
            // message received from some topic.
        } else {
            // normal downstream message.
        }

        // [START_EXCLUDE]
        /**
         * Production applications would usually process the message here.
         * Eg: - Syncing with server.
         *     - Store message in local database.
         *     - Update UI.
         */

        /**
         * In some cases it may be useful to show a notification indicating to the user
         * that a message was received.
         */
        sendNotification(message);
        // [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) {
        Intent intent = new Intent(this, MainActivity.class);
        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.drawable.ic_stat_ic_notification)
                .setContentTitle("GCM Message")
                .setContentText(message)
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }
}
MyInstanceIDListenerService
/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import android.content.Intent;

import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;

import com.google.android.gms.iid.InstanceID;
import com.google.android.gms.iid.InstanceIDListenerService;

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, RegistrationIntentService.class);
        startService(intent);
    }
    // [END refresh_token]
}
QuickstartPreferences
/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

public class QuickstartPreferences {

    public static final String SENT_TOKEN_TO_SERVER = "sentTokenToServer";
    public static final String REGISTRATION_COMPLETE = "registrationComplete";

}
RegistrationIntentService
/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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 com.google.android.gms.gcm.GcmPubSub;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;

import java.io.IOException;

public class RegistrationIntentService extends IntentService {

    private static final String TAG = "RegIntentService";
    private static final String[] TOPICS = {"global"};

    public RegistrationIntentService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);

        try {
            // [START register_for_gcm]
            // Initially this call goes out to the network to retrieve the token, subsequent calls
            // are local.
            // [START get_token]
            InstanceID instanceID = InstanceID.getInstance(this);
            String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
                    GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
            // [END get_token]
            Log.i(TAG, "GCM Registration Token: " + token);

            // TODO: Implement this method to send any registration to your app's servers.
            sendRegistrationToServer(token);

            // Subscribe to topic channels
            subscribeTopics(token);

            // 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(QuickstartPreferences.SENT_TOKEN_TO_SERVER, true).apply();
            // [END register_for_gcm]
        } catch (Exception e) {
            Log.d(TAG, "Failed to complete token refresh", e);
            // If an exception happens while fetching the new token or updating our registration data
            // on a third-party server, this ensures that we'll attempt the update at a later time.
            sharedPreferences.edit().putBoolean(QuickstartPreferences.SENT_TOKEN_TO_SERVER, false).apply();
        }
        // Notify UI that registration has completed, so the progress indicator can be hidden.
        Intent registrationComplete = new Intent(QuickstartPreferences.REGISTRATION_COMPLETE);
        LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
    }

    /**
     * Persist registration to third-party servers.
     *
     * 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 sendRegistrationToServer(String token) {
        // Add custom implementation, as needed.
    }

    /**
     * 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]

}
The RegistrationIntentService.java class is also where you put the code to send the registration information obtained from GCM to your app’s server. An example of that is COMING SOON.

Step 9: Fill in MainActivity.java Class

As before you will need to include your package name at the top of this class (this is not shown in the following code).

/**
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;

public class MainActivity extends AppCompatActivity {

    private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
    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_main);

        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(QuickstartPreferences.SENT_TOKEN_TO_SERVER, false);
                if (sentToken) {
                    mInformationTextView.setText(getString(R.string.gcm_send_message));
                } else {
                    mInformationTextView.setText(getString(R.string.token_error_message));
                }
            }
        };
        mInformationTextView = (TextView) findViewById(R.id.informationTextView);

        if (checkPlayServices()) {
            // Start IntentService to register this application with GCM.
            Intent intent = new Intent(this, RegistrationIntentService.class);
            startService(intent);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver,
                new IntentFilter(QuickstartPreferences.REGISTRATION_COMPLETE));
    }

    @Override
    protected void onPause() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);
        super.onPause();
    }

    /**
     * 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.
     */
    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.");
                finish();
            }
            return false;
        }
        return true;
    }
}

Step 10: Add in Strings

Copy the following code into the strings.xml file in the res directory in your program.

<resources>
    <string name="app_name">My Application</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="registering_message">Generating InstanceID token...</string>
    <string name="token_error_message">An error occurred while either fetching the InstanceID token,
        sending the fetched token to the server or subscribing to the PubSub topic. Please try
        running the sample again.</string>
</resources>

Step 11: Add in Colors

Create a colors.xml file in the res directory in your program and paste the following code.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="blue_grey_500">#607D8B</color>
    <color name="blue_grey_600">#546E7A</color>
    <color name="blue_grey_700">#455A64</color>
    <color name="blue_grey_800">#37474F</color>
    <color name="blue_grey_900">#263238</color>
</resources>

Step 12: Add in Layout

Here is the xml code for the layout of the app. It goes in activity_main.xml in the layout directory (located in the res directory of your program). Naturally, you can modify this layout as you desire—just be sure to update MainActivity.java to reflect any changes you make.

<LinearLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:background="@color/blue_grey_700"
    android:orientation="vertical" 
    tools:context=".MainActivity">

    <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" />

</LinearLayout>

Step 13: Next Steps

In the next section of this tutorial, I will walk you through how to implement an app that will let you send push notifications to the client app you just created.

UPDATE 4-18-2016: Part 2 IS coming (I promise), I have just been super busy and haven’t had a chance to update this site with my tutorials. I hope to get to it by this upcoming weekend or the end of the month at the latest. Thanks for your patience!