6-May-13 (Created: 6-May-13) | More in 'Parse Cloud'

Exploring Push Notifications with Parse

Please note that this is a real early draft!!

This article is about implementing push notifications in your Android Mobile applications through the parse cloud API.

when you publish mobile applications you want a way to communicate with the users of your mobile applications. You may also want to facilitate communications among the users themselves. These features of a mobile application are generally called Push Notifications.

In the last two articles on Parse I have covered how the Parse cloud is used to manage users and how to store objects on behalf of these users in the cloud. In this article I will cover the Push Notiifcations API of Parse. The features I am going to cover are

  1. How to intialize a mobile appplication so that it is enabled for Parse Push Notifications
  2. How to send notifications using the Parse dashboard
  3. How to send notifications using Parse dashboard to specific channels
  4. How to use mobile clients, instead of the dashboard, to send notifications to specific channels or a set of users
  5. How to capture push notifications through broadcast receivers

To demonstrate these concepts I will take the appplication that was developed for the last two articles and expand it with an additional activity that is able to both respond to push notifications and also send push notifications.

Much like the earlier articles on Parse let me start wtih the user experience of the application that I am going to use

A sample program to demonstrate Parse Push Notifications

If you were to run this application you will see a screen like this that lists the options available for a logged-in user

Welocme Screen to invoke the Test Push Activity

This is the same welcome screen from the previous article except there is an additionl button to invoke the activity that I will be using to both receive and send push notifications.

Respond To A Push

This activity that knows how to respond and send push notifications is shown below. In the java code later this activity is called RespondToPushActivity

Activity that knows how to respond and send Push Notifications

A few things about this activity. It is a multi purpose activity. The top of this activity has a text view that displays the contents of the intent that invoked this activity as a result of a push notification.

when I push a notification message from the Parse cloud, the notification message will show up in the notification bar at the top of your android device (or whereever that notification bar lies). When you pull down that notification message and tap or click on it, the activity shown here is invoked. First thing this activity does is get the intent that invoked it and display its details in at the top in the read only text view.

Looking at this textview we can see how Parse has delivered the message to an activity in your mobile application. When I push a message like this from server it is a server side push and is done through the Parse dashboard.

Push Notifications from a Client

There are three additional buttons in this RespondToPushActivity screen

The first one sends a message from this client to other clients using a direct API. The second button sends a message from this client to other clients using a JSON data object.

Both these options utilize something called push channeles. Each channel can be associated with an activity. In the sample application I have used this same RespondToPushActivity as the target for the channels as well. Because the RespondToPushActivity also investigates the intent that invoked it, this allows us to see what type of message is received when clients push notifications.

The third button sends a similar message from this client to other clients using channels, but instead of targeting an activity it targets a broadcast receiver. The broadcast receiver can then choose to notify using the notification manager giving it more flexibility as to what is shown in the notification.

Supporting Screens

Let me conclude this section by showing a few more supporting sceen shots.

Here is a screen shot of the Parse dashboard from where you can send messages to all clients.

Parse Push Notification Dashboard

Here is a screen shot that shows the page of the Parse Push Notification dashboard that is responsible for pushing the message

Push Notification Page

Here is a screen shot that shows what messages have been pushed

List of Pushed Messages

Here is a screen shot of how the message shows up on an Android device

Push notification displayed on an Android Device

Implementing the sample application using Parse Push

Implementing push notifications with Parse is really simple. Parse maintains an android service that is in constatnt touch with the parse cloud. This enables Parse to push messages to clients.

Each application that is installed on a device is represented by an "installation" parse object. Refer to the two previous articles what parse objects are. This installation object needs to be initialized when your application starts. This installation object is stored in the parse cloud for each application that is installed. This installation object tells the parse cloud what type of a device it is and if it is ready to receive messages or not etc.

Here is how a mobile application is initialized with this installation object.


public class ParseApplication extends Application {

    private static String tag = "ParseApplication";
    
    private static String PARSE_APPLICATION_ID
       = "....wqHaD2m";
    
    private static String PARSE_CLIENT_KEY
       = "....WGd2p";
    
    @Override
    public void onCreate() {
        super.onCreate();

        // Add your initialization code here
        Parse.initialize(this, PARSE_APPLICATION_ID, PARSE_CLIENT_KEY);

        //Security of data        
        ParseACL defaultACL = new ParseACL();
        // If you would like objects to be private by default, remove this line.
        defaultACL.setPublicReadAccess(true);
        ParseACL.setDefaultACL(defaultACL, true);
        
        //Enable to receive push
        PushService.setDefaultPushCallback(this, RespondToPushActivity.class);
        ParseInstallation pi = ParseInstallation.getCurrentInstallation();
        
        //Register a channel to test push channels
        Context ctx = this.getApplicationContext();
        PushService.subscribe(ctx, "ch1", RespondToPushActivity.class);
        
        pi.saveEventually();
    }
}

This code is quite similar to the one I have shown for the last two articles except the last section that contains push related initialization.

I have made use of the PushService class to indicate the default activity that should be invoked when the user taps the received message. I will show you shortly what the RespondToPushActivity code looks like.

I have also used the PushService to subscribe to all messages that are targeted for a user defined channel called "ch1". Channels are merely a way to classify messages and allow publish/subscribe kind of functionality. It is important to note that Parse dashboard has no explicit place to create or maintain these channel strings (their names). It is upto the developer to design these channel string names as needed.

For example you can device three activities that you want to target when you receive messages. Then you create three channels and call them "ch1", "ch2", and "ch3". And when you target messages to these channels then the respective activities will be invoked.

Or one of the users of your application may create channels through your application. Then the other users could subscribe to them. In that case as a developer you are responsible for keeping track of this meta data like any other parse object.

And finally you get the current installation object and save it to the parse cloud by calling saveEventually() or saveInTheBackground(). Earlier versions of Parse has some issues with saveInTheBackground() while another server side call may be in progress like the Parse.initialize() on the same thread. This is rectified in recent releases such as the one I have used to test, 1.2.3 of Parse.

To complete the initialization you also need the following entries in the manifest file of your application


<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />

The permission tags usually go right above the application tag and are siblings of the application tag.

Inside the application tag you need the following


<service android:name="com.parse.PushService" />
<receiver android:name="com.parse.ParseBroadcastReceiver">
  <intent-filter>
    <action android:name="android.intent.action.BOOT_COMPLETED" />
    <action android:name="android.intent.action.USER_PRESENT" />
  </intent-filter>
</receiver>

Both PushService and the ParseBroadcastReceive in the code above are part of the Parse SDK.

Sending a push notification from the Parse Dashboard

Lets see the code for the RespondToPushActivity class that is targeted by the push notifications of the sample application.


public class RespondToPushActivity 
extends BaseActivity 
{
    private int i = 0;
    public RespondToPushActivity() {
        super("RespondToPushActivity");
    }
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.rtp_respond_to_push);
        examineIntent(getIntent());
    }
    //This activity is setup as a singletop
    @Override
    protected void onNewIntent(Intent intent) {
        examineIntent(intent);
    }
    
    //Use Java methods to push a message
    public void sendMessage(View v)
    {
        ParsePush push = new ParsePush();
        String message = "Client message" + Integer.toString(i++);
        push.setChannel("ch1");
        push.setMessage(message);
        push.sendInBackground();
    }
    
    //Use a JSON object to send a message
    public void sendMessageAsData(View v)
    {
        JSONObject data = getJSONDataMessage();
        ParsePush push = new ParsePush();
        push.setChannel("ch1");
        push.setData(data);
        push.sendInBackground();
    }
    private JSONObject getJSONDataMessage()
    {
        try
        {
            JSONObject data = new JSONObject();
            data.put("alert", "Main Message");
            data.put("customdata", "custom data value");
            return data;
        }
        catch(JSONException x)
        {
            throw new RuntimeException("Something wrong with JSON", x);
        }
    }
    
    //Use JSON data to send a message to a broadcast receiver
    public void sendMessageAsIntent(View v)
    {
        JSONObject data = getJSONDataMessageForIntent();
        ParsePush push = new ParsePush();
        push.setChannel("ch1");
        push.setData(data);
        push.sendInBackground();
    }
    //Notice how the 'action' attribute enables the 
    //broadcast receiver behavior.
    private JSONObject getJSONDataMessageForIntent()
    {
        try
        {
            JSONObject data = new JSONObject();
            //Notice alert is not required
            //data.put("alert", "Message from Intent");
            //instead action is used
            data.put("action", TestBroadcastReceiver.ACTION);
            data.put("customdata", "custom data value");
            return data;
        }
        catch(JSONException x)
        {
            throw new RuntimeException("Something wrong with JSON", x);
        }
    }
    //Populate the textview with the intent that has the message
    private void examineIntent(Intent i)
    {
        String u = i.toURI();
        TextView tv = (TextView)findViewById(R.id.rtp_welcomeMessage);
        tv.setText(u);
    }
}//eof-class

The layout for this RespondToPushActity is shown in one of the earlier figures above that contains the display of the incoming intent and the 3 buttons to send messages. There are a number of things that are going on in this activity. All of the send methods are not required if you want this activity to be just a receiver of the push notification message.

In the oncrete() method of this activity I have taken the intent and populated the text view. You can see this in the method examineIntent(). I have used the "toUri()" method on the intent to get a string representation of the intent. This will tell us how Parse is constructing the message to be delivered to the activity.

Also as you may receive other messages targeted for this same activity I have indicated the activity as single top in the manifest file. Here is that code.


<activity android:name="com.androidbook.parse.RespondToPushActivity"
    android:launchMode="singleTop"
    android:label="Respond"/>

The implication of being a singletop is that onCreate() won't be called if this activity is on the top of the stack. Instead the onNewIntent() method is called. that is why the code above takes onNewIntent() into consideration. If I don't do this then everytime a new message arrives and user taps on it a new activity is created on the stack. By specifying it as singletop I avoid multiple activities on the stack.

With this RespondToPushActivity in place, now if you send a message from the server you will see first an alert at the top of your android device. Then you choose the notification after dragging the notification panel down, by clicking on it you will be taken to this RespondToPushActivity and the intent will be displayed on the top. You will be able to see the message that you typed in the dashboard available in the intent as you may likely want to take some action using this message text.

Sending a message on a particular channel using the dashboard

Because I have subscribed to a channel called "ch1" in the initialization process, parse.com knows that there is a channel called "ch1" that can be targeted. In the parse push notification dashboard you will be able to choose this channel as the target. That message will then go to all the clients that have subscribed to this channel and end up invoking the same RespondToPushActivity as this activity is registered as the target activity for that channel.

Sending a message through the client

Take a look at the sendMessage() method in the code of RespondToPushActivity presented earlier and reproduced here.


//Use Java methods to push a message
public void sendMessage(View v)
{
    ParsePush push = new ParsePush();
    String message = "Client message" + Integer.toString(i++);
    push.setChannel("ch1");
    push.setMessage(message);
    push.sendInBackground();
}

In this code snippet I am using an object called ParsePush to set the channel and set a message and then send it in the background. This message will show up and ultimately result in invoking the same RespondToPushActivity.

However on the parse dashboard you need to enable client side push first. Go to parse.com and access the dashboard for your application. You will see the settings for push whre you will find this client side push setting.

Sending a message as data from client side

When you are using the ParsePush object you may be wondering whether it is possible to send additional data other than a message? The answer is yes. This is done by using the setData() method. this methods takes a JSON object as its input. Being a JSON object this data object then allows any number of key value pairs that could be sent to the receivers.

Even when we used the ParsePush.setMessage() method, underneath it gets converted to JSON object with a set of predefined keys. The message that is used as part of the setMessage() goes as the key "alert". I will cover another reserved key called "action" later in the article. For the full list of these keys see online Parse documentation.

My goal in this part of the article is to send an extra data element called "customdata" and see if this field becomes accessible in the RespondToPushActivity.

The code below shows how to create a JSON data object and then send it as a parse push message.


//Use a JSON object to send a message
public void sendMessageAsData(View v)
{
    JSONObject data = getJSONDataMessage();
    ParsePush push = new ParsePush();
    push.setChannel("ch1");
    push.setData(data);
    push.sendInBackground();
}
private JSONObject getJSONDataMessage()
{
    try
    {
        JSONObject data = new JSONObject();
        data.put("alert", "Main Message");
        data.put("customdata", "custom data value");
        return data;
    }
    catch(JSONException x)
    {
        throw new RuntimeException("Something wrong with JSON", x);
    }
}

There doesn't appear to be a way to send push messages from the client side to ALL the clients. Client side push messages seem to require a channel to publish on. But again if all of your mobile applications subscribe to a single well known channel publishing on that channel will go to all clients.

Using the client side push you can also give a collection of Installation objects to ParsePush. I am not covering that approach in this article. However it is quite easy to understand how to do that. You get a query object on the Installation class and then specify the where clause of all the types of installation objects you want to target.

when you are using a JSON data object it is important what keys I am using to send the data. So do refer to the Parse documentation to get a handle on the available keys.

Using Broadcast Receivers as targets for push notifications

By adjusting what key we use in the JSON data object we can make parse to invoke a broadcast receiver instead of triggering a notification that shows up in the notification bar. When you do this no notification will be sent to the notification manager. Only thing that parse does is to invoke a broadcast receiver that you specify.

Here is how you prompt Parse to do this: construct a JSON data object with a key called "action" that points to an android intent whose action invokes a broadcast receiver.

Code below shows how create such a JSON data object and send it as a push notification.


//Use JSON data to send a message to a broadcast receiver
public void sendMessageAsIntent(View v)
{
    JSONObject data = getJSONDataMessageForIntent();
    ParsePush push = new ParsePush();
    push.setChannel("ch1");
    push.setData(data);
    push.sendInBackground();
}
//Notice how the 'action' attribute enables the 
//broadcast receiver behavior.
private JSONObject getJSONDataMessageForIntent()
{
    try
    {
        JSONObject data = new JSONObject();
        //Notice alert is not required
        //data.put("alert", "Message from Intent");
        //instead action is used
        data.put("action", TestBroadcastReceiver.ACTION);
        data.put("customdata", "custom data value");
        return data;
    }
    catch(JSONException x)
    {
        throw new RuntimeException("Something wrong with JSON", x);
    }
}

when you use the action attribute make sure not to use the "alert" also at the same time. If you do, Parse seem to do both: invoke the broadcast receiver and also send the notification.

The action attribute of the JSON object is pointint to the constant TestBroadcastReceiver.ACTION. This is a string which is defined in the TestBroadcastReceiver.

Here is the code for the TestBroadcastReceiver. Notice that the value of the action is: com.androidbook.parse.TestPushAction


public class TestBroadcastReceiver 
extends BroadcastReceiver 
{
    public static final String ACTION="com.androidbook.parse.TestPushAction";
    public static final String PARSE_EXTRA_DATA_KEY="com.parse.Data";
    public static final String PARSE_JSON_ALERT_KEY="alert";
    public static final String PARSE_JSON_CHANNELS_KEY="com.parse.Channel";
        
    private static final String TAG = "TestBroadcastReceiver";
     
    @Override
    public void onReceive(Context context, Intent intent) 
    {
        try 
        {
          String action = intent.getAction();
          
          //"com.parse.Channel"
          String channel = 
              intent.getExtras()
                  .getString(PARSE_JSON_CHANNELS_KEY);
          
          JSONObject json = 
              new JSONObject(
                      intent.getExtras()
                         .getString(PARSE_EXTRA_DATA_KEY));
     
          Log.d(TAG, "got action " + action + " on channel " + channel + " with:");
          Iterator itr = json.keys();
          while (itr.hasNext()) 
          {
              String key = (String) itr.next();
              Log.d(TAG, "..." + key + " => " + json.getString(key));
          }
          notify(context,intent,json);
        } 
        catch (JSONException e) 
        {
            Log.d(TAG, "JSONException: " + e.getMessage());
        }
    }
    private void notify(Context ctx, Intent i, JSONObject dataObject)
    throws JSONException
    {
          NotificationManager nm = (NotificationManager)        
             ctx.getSystemService(Context.NOTIFICATION_SERVICE);
          
          int icon = R.drawable.robot;
          String tickerText = 
              dataObject.getString("customdata");
          long when = System.currentTimeMillis();          
          Notification n = new Notification(icon, tickerText, when);
          
          //Let the intent invoke the respond activity
          Intent intent = new Intent(ctx, RespondToPushActivity.class);
          //Load it with parse data
          intent.putExtra("com.parse.Data", 
                  i.getExtras().getString("com.parse.Data"));
          
          PendingIntent pi = PendingIntent.getActivity(ctx, 0, intent, 0);

          n.setLatestEventInfo(ctx, "Parse Alert", tickerText, pi);
          n.flags |= Notification.FLAG_AUTO_CANCEL;
             
          nm.notify(1, n);          
    }
}//eof-class

Following the rules of broad cast receivers this receiver needs to be defined in the android manifest file like below


<receiver 
    android:name=".TestBroadcastReceiver"
    android:exported="false"
    >
    <intent-filter>
      <action android:name="com.androidbook.parse.TestPushAction" />
    </intent-filter>
</receiver>        

Notice that the action name in the json object matches the action filter for the test broadcast receiver. This is how the TestBroadcastReceiver is invoked when this message is pushed to the android devices.

I am doing two things in this test broadcast receiver. I am first retrieving the JSON data object that Parse sends through the intent. From this JSON object I can extract the standard fields and also any custom fields I may have prepared. Next, as is the good practice, I constructed my own notification and send it to the notification manager. I have also set a new intent on the notification so that the same RespondToPushActivity is invoked so that we can monitor what is in the intent.

Summary

Parse.com can take your mobile applications to places where it hasn't gone before. As an author of your mobile app you will be able communicate with your user base better. It is possible to write collaborative applications that can bring the power of groups and community to the benefit of everyone.

Parse.com enables this by providing storage, user management, and push notifications through a really nice API and a web based dashboard. With Facebook acquiring parse I expect to see this platform become really mature and even possibly leap frog with new ideas in the next couple of years.

This article gives an excellent inroduction to some of the things that you can do with parse push notifications. For more features see the parse website for additional things that either I have mentioned in passign remarks, or haven't mentioned at all.

Questions

This article tried to answer the following questions

1. How does Parse accomplish push notifications?

2. How do I setup my application to work with Parse push notifications?

3. What kind of dashboard support is there to work with push notifications in Parse?

4. What are installation objects?

5. What are the Parse services and receivers that need to be registered in your application?

6. What are channels?

7. How does one manage channels? Do they need to be predefined?

8. How do you send messages only to certain devices based on a query?

9. How do you write Broadcast receivers that can be triggered by push messages?

10. what is the difference between clients sending push messages and a server through a dashboard sending push messages?

11. what is the role of JSON data objects in sending and receiving push notifications?

12. How do you retrieve and construct a JSON data object using intent extras?

References

All the references from the previous articles are still applicable here. See the right hand side to see these previous articles and also other references.

Here are the links for the 2 previous articles for a quick reference.

Article1: Parse for data storage

Article2: Passing Parse through Parcelables

In addition the following links are pertinent to some of the special topics covered in this article.

Parse Push Notifications Guide

Working with Broadcast Receivers

Working with Notification Managers