Tuesday 10 June 2014

Google Cloud Messaging for Dummies!

So I am working on something pretty big for BreakFree. I am introducing a lot of gamification in the app. To give you an idea, my users will be able to view their Facebook friends on BreakFree, what their addiction score is and they can also challenge their friends to see who can get a better score. In any case, the long and short of it is that I needed GCM in my app. And to be very honest I couldn't find a decent tutorial on it. Most of them dated back to the C2DM era. So I thought of putting one together. 

This is going to be the simplest GCM implementation ever, by itself it's not going to add much value to your app, but you should have a skeleton ready after which you can add the real meat. I assume you know a little bit of Android programming and you won't cry when you see PHP scripting. I am assuming you know what GCM is intended to do. If not you can read it here.

1. Register app on Google Developers Console
Ok so before we start with the fun stuff you will need to register your app on the Google Developers Console by creating a project, enabling the GCM service and obtaining an API key. Follow the Getting Started section of GCM (GCM - Getting Started). Please make note of the Project Number and API Key got from this step.

2. Register user's device with the GCM service
Before we dig into this step you must understand that for GCM to work every device associated with your app needs to have a unique ID provided by the GCM service. This ID will be used by you to send notifications to these devices. To register you need to get an instance of GoogleCloudMessaging and run the register method. But you need to run this on a separate thread, hence we use a AsyncTask. Check the code below:

//async class to register with GCM in background
private class registerGCM extends AsyncTask<Void, Void, String> {
GoogleCloudMessaging gcm;

@Override
protected String doInBackground(Void... params) {
gcm = GoogleCloudMessaging.getInstance(ctx);
try {
  String regId = gcm.register(ctx.getString(R.string.google_project_id));
  
  //store reg id in SP
  spEdit.putString(ctx.getString(R.string.SPCRegID), regId);
  spEdit.commit();
catch (IOException e) {
  e.printStackTrace();
}
return null;
}

protected void onPostExecute(String regid) {
  //call function to update fb_id and then send data to server
  new storeRegID().execute("update fb_id");
}
}

In the code above I create an instance of GoogleCloundMessaging (ctx is my Context variable), register the device on GCM by providing the project number got in Step 1. I store the ID in SharedPreferance. And finally in onPostExecute() I call the another AsyncTask to store the ID on my server (described below)

3. Send GCM Reg ID on our server
We need to store our users reg ID's, provided by GCM, on our server. These ID's then need to be looked up whenever a user needs to send a notification to another user, or when you want to send notifications to your users from the server. Here is the Android code that sends the reg ID along with the user's name (this can be any unique identifier for your user).

//async class to send data reg id and unique id to server
private class sendDataToServer extends AsyncTask<String, Void, String>{

@Override
protected String doInBackground(String... vals) {

    //get reg id
    String regId = spObj.getString(ctx.getString(R.string.SPCRegID), null);

   //get name
   String name = "Mrigaen Kapadia"

   // add values to be sent
   List<NameValuePair> params = new ArrayList<NameValuePair>();
   params.add(new BasicNameValuePair("name", name));
   params.add(new BasicNameValuePair("regId", regId));

   // getting JSON Object
   JSONParser jsonParser = new JSONParser();
   JSONObject json = jsonParser.makeHttpRequest(ctx.getString(R.string.php_send_data), "POST",        params);

   // check for success tag
   try {
            int success = json.getInt("success");
            String message = json.getString("message");                
               
            if (success == 1) {                
                Toast.makeText(ctx, "Data Saved on Server", Toast.LENGTH_LONG).show();
            }
            // failed to create product
            else {
                Toast.makeText(ctx, "Failed to Save Data on Server", Toast.LENGTH_LONG).show();
            }
   } catch (JSONException e) {
                e.printStackTrace();
   }

  return null;
  }
}

Ok, so simple stuff, we get the reg ID stored in Step 2, and any other unique id of the user, like his name, Facebook id, etc. Mind you it is this unique id that other users will use to send a notification to this user. Both these are passed into a function of the JSONParser class (shown below), along with the name of the PHP script which we need to call and "POST" constant.

In the try/catch block we check if the posting on the server was a success by capturing the value retrieved from the PHP script. You will understand this better when you see the JSONParser class and the PHP script.

4. JSONParser Class
This is a utility class created to parse the information (read the NameValuePairs) sent to it and send over this information to the PHP script mentioned as a parameter. Lets see how this is done.


public class JSONParser {
    static InputStream is = null;
    static JSONObject jObj = null;
    static String json = "";

    // constructor
    public JSONParser() {}

    // function get json from url   
    public JSONObject makeHttpRequest(String url, String method, List<NameValuePair> params) {
     
        // Making HTTP request
        try {

            // check for request method
            if(method == "POST"){
                // defaultHttpClient
                DefaultHttpClient httpClient = new DefaultHttpClient();

                // send over params to url
                HttpPost httpPost = new HttpPost(url);
                httpPost.setEntity(new UrlEncodedFormEntity(params));
                
                //get response from server and store in a Input Stream
                HttpResponse httpResponse = httpClient.execute(httpPost);
                HttpEntity httpEntity = httpResponse.getEntity();                
                is = httpEntity.getContent();
            }          

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
             //read the response from the server
            BufferedReader reader = new BufferedReader(new InputStreamReader(is, "iso-8859-1"), 8);
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) { 
                sb.append(line + "\n");
            }
            is.close();
            json = sb.toString();
        } catch (Exception e) {
            Log.e("Buffer Error", "Error converting result " + e.toString());
        }

        // try parse the string to a JSON object
        try {        
            jObj = new JSONObject(json);
        } catch (JSONException e) {
            Log.e("JSON Parser", "Error parsing data " + e.toString());
        }

        // return response from server back to calling function
        // this should ideally contain values for "success" and "message" from the server
        return jObj;

    }
}

As you can see, this all important class gets the NameValuePairs, sends them as a HttpPost to the URL specified. And waits for a response, which is then received by the calling function.

5. PHP Script to Store Name and RegID
Ok, so here we require some php scripting. Don't worry this is not too difficult. I didn't know any php before I started working on this update for BreakFree. 

<?php
ini_set('display_errors', 'On');
error_reporting(E_ALL);
/* The following code will create a user in the breakfree_users table */

// array for JSON response

$response = array();

// check for required fields

if (isset($_POST['name']) && isset($_POST['regId'])) {

    $name = $_POST['name'];
    $regId = $_POST['regId'];
    $notification = False;
    $num_rows = 0;
    
    // include db config class
    require_once __DIR__ . '/db_config.php';

    // connecting to db
    $mysqli = new mysqli(DB_SERVER, DB_USER, DB_PASSWORD, DB_DATABASE);
           
    // inserting a new row
    if($stmt = $mysqli->prepare("INSERT INTO table(name, reg_id) VALUES(?, ?)")){
   
       // Bind the variables to the parameter as  strings. 
       $stmt->bind_param("ss", $name, $regId);
   
      // Execute the statement.
      $result = $stmt->execute();
       
      // Close the prepared statement.
      $stmt->close();
   }        
    
    //if record inserted/updated correctly
    if ($result) {
        // successfully inserted into database
        $response["success"] = 1;
        $response["message"] = "User successfully created.";

        // echoing JSON response
        echo json_encode($response);
    }
    //record failed to be updated 
    else {
        // failed to insert row
        $response["success"] = 0;
        $response["message"] = "Oops! An error occurred.";

        // echoing JSON response
        echo json_encode($response);
    }

else {
    // required field is missing
    $response["success"] = 0;
    $response["message"] = "Required field(s) is missing";

    // echoing JSON response
    echo json_encode($response);
}

?>

As you can see this is a bare-bones script. You will obviously need to add some checks like if the record exists, etc. But this should suffice for our tutorial.

6. Send Unique ID of Receiver to the Server
To understand this piece, imagine that you are now on the sender's instrument. And this piece of code is run on the person's device who wants to send a notification to his friend. In this step we identify the name of the friend who needs to receive this notification along with the message and send it to our server. This name is on our server from Step 3.


//Async task to send unique id of GCM receiver to our sender

private class sendChallenge extends AsyncTask<String, Void, String> {

    String receiver = "Mrigaen Kapadia";
    String sender = "John Wayne";
    String message = "I need your help!";

    List<NameValuePair> params = new ArrayList<NameValuePair>();
    params.add(new BasicNameValuePair("receiver"receiver));
    params.add(new BasicNameValuePair("sender", sender);
    params.add(new BasicNameValuePair("message"message)); 

   // getting JSON Object
   // Note that create product url accepts POST method
   JSONParser jsonParser = new JSONParser();
   JSONObject json = jsonParser.makeHttpRequest(ctx.getString(R.string.php_send_name), "POST",   params);

    // check for success tag
    try {
           int success = json.getInt("success");                
               
           if(success == 1)
               return "success";
           else
               return "fail";                                                
      } 
      catch (JSONException e) {
                e.printStackTrace();
      }

      return "fail";
}

protected void onPostExecute(String result) {
     if (result.equals("success"))
            Toast.makeText(ctx, "name of receiver sent", Toast.LENGTH_SHORT).show();                        
      // failed to send name
      else
            Toast.makeText(ctx, "failed to send name to server", Toast.LENGTH_SHORT).show();
    }
}

Ok, so the name, as explained before is any unique ID that is stored by your program for individual users. The sender passes on the name of the receiver to our server, using the JSONParser class.

7. Send Reg ID and Other Params to GCM
This is probably the most important step in the tutorial. Here our PHP script picks up the receiver's name sent in Step 6, gets the corresponding Reg ID (stored in Step 5), and sends the Reg ID the message and the sender's name to GCM

<?php
ini_set('display_errors', 'On');
error_reporting(E_ALL);

/* The following code will receive the receivers name and other data to be sent and send the necessary information to GCM */

//array for JSON response
$response = array();

// include db connect class
require_once __DIR__ . '/db_connect.php';

//API Key
$api_key = <Your API Key from Step 1>;

// check for required fields
if (isset($_POST['receiver']) && isset($_POST['sender']) && isset($_POST['message'])) {
$receiver = $_POST['receiver'];
$sender = $_POST['sender'];
$message = $_POST['message'];
//get registration id for receiver
$result_reg_id = mysql_query("SELECT reg_id FROM breakfree_users WHERE name = '$receiver'");
    $num_rows = mysql_num_rows($result_reg_id);
   
    //reg id exists for b_fb_id
    if($num_rows > 0){
    $row = mysql_fetch_array($result_reg_id, MYSQL_ASSOC);
    $reg_id = $row["reg_id"];    
   
    // Set other POST variables for GCM
$url = 'https://android.googleapis.com/gcm/send';
$fields = array("registration_ids" => array($reg_id), "data" => array("sender" => $sender, "message" => $message);
$headers = array("Authorization: key=$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);
       
       // Disabling SSL Certificate support temporarly
       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);        
       
       // successfully inserted into database
       $response["success"] = 1;
       $response["message"] = "Challenge sent";
               
       // echoing JSON response
echo json_encode($response);              
    }
    //no reg id found for b_fb_id
    else{
    // required field is missing
    $response["success"] = 0;
    $response["message"] = "Reg Id not found for Challenger";

    // echoing JSON response
echo json_encode($response);    
    }
}
else {
    // required field is missing
    $response["success"] = 0;
    $response["message"] = "Required field(s) is missing";

    // echoing JSON response
    echo json_encode($response);
}       
?>

The PHP script above picks up the receiver's name, sender's name and message sent by the sender in Step 6. It then looks up the Reg ID of the receiver (this should have been stored in Step 5). After this it sends off this information to GCM. The GCM url as shown above is 'https://android.googleapis.com/gcm/send'. The registration id needs to passed onto GCM in an array with the 'registration_ids'. Any other information, in our case sender and message, needs to passed in the same array with the key 'data'. Also pay close attention to the header format. This needs to be 'Authorization: key=$api_key", "Content-Type: application/json'. There are other options you can use. Visit the official Android page to know more.

8. Finally, Receive the Message on the Receiver's Handset
Now imagine you are on your receiver's handset. We need this piece of code to receive the message from GCM sent by the sender. First we must declare a Broadcast Receiver in our manifest to intercept the GCM message:

Add the following permissions:
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission
        android:name="<your package name>.C2D_MESSAGE"
        android:protectionLevel="signature" />

Add the receiver:
<application...
        <receiver
            android:name=".GCMBroadcastReceiver"
            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=<your package name> />
            </intent-filter>
        </receiver>
</application>

The GCMBroadcastReceiver class will look something like this:
public class GCMBroadcastReceiver extends BroadcastReceiver {

     @Override
      public void onReceive(Context ctx, Intent i) {
              Bundle extras = i.getExtras();
              
              //get sender name and message
              String sender = extras.getString("sender");
              String message = extras.getString("message");

              String notification = sender+" says "+message;

              //create notification or whatever here....
        }
}

As seen above the GCM parameters are received in the Intent passed onto the receiver. You can extract them with the keys you passed in the 'data' constant in Step 7. From here you can do whatever you need to with the received data, ideally you would create a notification or something like that.

Hope this simplified GCM tutorial helped you. Do leave your feedback/questions in the comments.

Until next time.