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.


Wednesday, 19 March 2014

The All Important setDeleteIntent for Notifications

My app, BreakFree, notifies the user in case of any event like using the phone for a total of 1 hour, or launching a particular app 15 times or more, etc. Now, there is also an option in my app wherein the user gets to see his phone addiction score as a persistent notification. This is updated at every unlock. My problem here was that I did not want to overwrite the event notification with the addiction score notification till the user clears the event notification himself. 

Now, call me dumb, but for the love of God I found it impossible to find documentation on how to detect the user clearing the notification. After a lot of searching I found the missing link. Actually there are two. setDeleteIntent and setContentIntent.

Here is how I coded the solution:

1) In my function which updates my notifications, I created two pendingIntents, one two be called when the user deletes my notification (swiping it, or using the Clear All button). And the second to be called when the user clicks the notification. Both call the same receiver class (NotificationReceiver), but with different Extras. Pay particular attention to the flags for the Pending Intent.

//create pending intent to be called when user deletes the notification
Intent notiDeleted = new Intent(ctx, NotificationReceiver.class);
notiDeleted.putExtra(ctx.getString(R.string.noti_deleted), true);
PendingIntent notiDeletedIntent = PendingIntent.getBroadcast(ctx, 0, notiDeleted, 0);
   
//create pending intent to be called when user clicks the notification
Intent notiClicked = new Intent(ctx, NotificationReceiver.class);
notiClicked.putExtra(ctx.getString(R.string.noti_clicked), true);
PendingIntent notiClickedIntent = PendingIntent.getBroadcast(ctx, 1, notiClicked, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);


2) I then created a SharedPreference flag saying that the notification is still present in the notification tray. This flag tells my code not to show the second notification.

//create a flag that an event noti is being shown
spEdit.putBoolean(ctx.getString(R.string.SPCEventNotiVisible), true);
spEdit.commit();

3) Build the notification. Use the setDeleteIntent and setContentIntent.
// build notification
NotificationCompat.Builder nb = new NotificationCompat.Builder(ctx);
nb.setSmallIcon(R.drawable.ic_jump_outline);
nb.setContentIntent(notiClickedIntent);
nb.setDeleteIntent(notiDeletedIntent);
nb.setAutoCancel(true);

Notification bfN = nb.build();

4) Next we create our receiver class.
public class NotificationDeleteReceiver extends BroadcastReceiver{

@Override
public void onReceive(Context context, Intent intent) {

  //make flag to noti not shown
  SharedPreferences spObj =
  context.getSharedPreferences(context.getString(R.string.SPPrevSetting), 0);
  SharedPreferences.Editor spEdit = spObj.edit();
  spEdit.putBoolean(context.getString(R.string.SPCEventNotiVisible), false);
  spEdit.commit();

  //if called from user click then show app
  if(intent.getExtras().containsKey(context.getString(R.string.noti_clicked))){
Intent i = new Intent(context, MainScreen.class);
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |            Intent.FLAG_ACTIVITY_CLEAR_TOP | 
        Intent.FLAG_ACTIVITY_SINGLE_TOP);                
i.putExtra(context.getString(R.string.noti_on_click),                              intent.getExtras().getString(context.getString(R.string.noti_on_click)));
context.startActivity(i);
}
  }

}

As you can the receiver changes the SharedPreference flag to false regardless of whether the notification was cleared or clicked. It then checks the extras and if the notification was clicked it then opens up the app.

Hope this helps you guys.

Monday, 10 February 2014

What was Google Thinking When They Did This?

Yeah, I'm talking about the totally ridiculous 'Rate this app to get recommendations' gig by Google.

So here is what happened to me the other day. I open up Play Store, and I get the Rate this app... thingy and its asking me to rate an app I uninstalled about 6 months ago. Now I download about an app a day, and I have no freekin idea about that app, was it good? Did it crash on me? Did I get bored of it? Umm.. no.. no idea. But I still want the recommendations.. So being the good guy I am I give it 5 stars and proceed. BUT.. the problem is we are not living in a world filled with good guys, I am sure many would have just given it a one star rating and moved on. Yeah, check out this link by androidpolice.

This is where my problem lies as a developer. I have a stickiness ratio of about 40% on my apps, which means 60% of my installs result in uninstalls. And imagine google asking these guys to rate my app.. I mean c'mon some of them may not even have used my app. 

I see what Google's trying to do here, they want apps rated which helps them in ranking the apps, fair enough. NO.. not fair. Cause a majority of these ratings are not even legit. They've been given under the temptation of getting more recommendations like my app. 

All I can do is hope that there are more good guys like me out there.

Saturday, 8 February 2014

Reducing Image Sizes to Reduce the Size of Your apk

Wanted to write a quick blog post on this wonderful tool I found called ImageOptim. So the apk size of my app, BreakFree, had gone to 7mb. Which I was not very comfortable with. Many users, being on limited data, are fussy about downloading apps over 2-3 mb. My app was clearly well over. 

One of the things I had never considered before was reducing the size of my images. And the most easiest ways I found was by using this tool called ImageOptim. You can get it at imageoptim.com. Its super easy to use, unlike the other tools I explored. And it does a decent job with the images as well.

All my images are .pngs saved mostly on full compression out of GIMP. By processing them through ImageOptim I got an average reduction of about 16%-17% on my file sizes. I managed to reduce my apk size to 5.5mb. A far cry from the ideal 3 mb, but at least its better than 7mb.

Sunday, 2 February 2014

Why the Mobile App Business Needs Immediate Intervention

The other day I read an article on why the mobile app business is NOT the business to be in. Some parts of the article made sense, and even though I want to rubbish the article, there is one part of me that believes in what the author wrote. 

We usually only want to believe and hear the rosy stories. The 18 year old who made millions by selling an app he developed in high school, or about the guy who moved from a one bedroom apartment to a mansion in Florida. Lets face it, the chances of that happening to us is zilch. Not because we cannot make decent apps, but because the app stores are now overcrowded. Yeah, theres tons of apps out there. And the worse part is most of these apps are free. I mean think about it, if there is a butt ugly app out there, doing 70% of the things that your charming app does, and is free, your out of the race man. No man in his right state of mind is going to pay 99 cents for your wonderful app if there is a free substitute out there.

Now its because of this precise reason that the ridiculous concept of in-app purchasing has blossomed. We cannot charge for our apps, but we have to make money, so what do we do? We give our apps away for free, in the hope that maybe 1% of the folks who download our app, purchase a part of it. The concept of in-app purchases is just so flawed. If it were to happen in retail, we would have Walmart selling free shampoos without caps. If you want to walk out with a cap on your shampoo, please pay 99 cents. 

This is where I think Google and Apple should intervene. If they want to make a business out of their app stores, want healthy competition, then they better do something. I don't know what, I really don't have a solution, but I hope they find one fast.

Monday, 20 January 2014

BreakFree is Officially Published

Hello peoples. Its been two days since the launch of BreakFree, and the response has been decent. I've collected about 75 odd downloads with seven people rating it (all 5 stars as yet, woohoo). I would have obviously liked the download numbers to be better, don't we all hope for our apps going viral, but this is still ok. Considering this is a beta launch and I did not marketing whatsoever, except for posting comments in some articles and Youtube videos.

I have already made two updates due to the bugs found by users. I have also created quick and dirty website (it seriously was quick, about 3 hrs to learn Wordpress and create the website). You can check it out by clicking the 'BreakFree' link under 'My Apps'.

My strategy is to wait till I get about 500 downloads, after which I start my heavy duty promotions. I am still coming up with a plan for the promotion piece. Will share that you as well.

Attaching a few screenshots of the app. You can get more detail by visting the website or the Google Play Store page.

Have fun.