Monday, May 27, 2013

Push Notification

Push notifications provides a way to notify your mobile application on new message or event that is related to your application. Push notification are received and displayed regardless to the application running status. 
When a device receive push notification the application icon and a message appears on the device status bar. If the user press the notification message he is directed to your application.

Using Parse.com as notifications server

In this post i will show how to use push notifications from Parse.com. 
The tutorial will demonstrate:
1.  How to receive the notification and display it
2.  How to implement popup message upon receiving push notification. (I am leaving out the decision what is prefer by the users status bar notification or popups)

The first step is to have a Parse.com account  and open and application in the Parse.com dashboard.
Create Android empty project and add Parse SDK jar to your lib directory.
On your Android application:
1. Initialize parse SDK with your application,client keys.
2. Register to Parse notification services:

 protected void onCreate(Bundle savedInstanceState) {  
           super.onCreate(savedInstanceState);  
           setContentView(R.layout.activity_main);  
         //Parse SDK Init  
           Parse.initialize(this, Applicatio Id, Client ID);  
           PushService.setDefaultPushCallback(this, MainActivity.class);  
           ParseInstallation.getCurrentInstallation().saveInBackground();  
      }  

In the manifest file add the following permissions:

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

Register receiver just before the closing </application> tag :


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

At this point your application is ready for receiving push notification in order to test it go to Parse dashboard and select Push notification tab and send notification .

Check the device /emulator status bar to see the notification message.

Implementing Push Notification Receiver

Lets implement our own receiver that will open popup dialog. Parse API provide way to send the push notification as JSON object that encapsulate more data inside the notification. The supported JSON object includes the intent that should be triggered when the notification received.

First change the manifest and add the following receiver :


 <receiver android:name="com.iakremera.pushdemo.MyCustomReceiver" >  
       <intent-filter>  
         <action android:name="android.intent.action.BOOT_COMPLETED" />  
         <action android:name="android.intent.action.USER_PRESENT" />              
         <action android:name="com.iakremera.pushdemo.UPDATE_STATUS" />  
       </intent-filter>  
 </receiver>  

The bold action represent the information that will be sent from the server in order to fire the intent.
The receiver code look like :

 public class MyCustomReceiver extends BroadcastReceiver {  
      private static final String TAG = "MyCustomReceiver";  
      @Override  
      public void onReceive(Context context, Intent intent) {  
           try {  
                if (intent == null)  
                {  
                     Log.d(TAG, "Receiver intent null");  
                }  
                else  
                {  
                     String action = intent.getAction();  
                     Log.d(TAG, "got action " + action );  
                     if (action.equals("com.iakremera.pushdemo.UPDATE_STATUS"))  
                     {  
                          String channel = intent.getExtras().getString("com.parse.Channel");  
                          JSONObject json = new JSONObject(intent.getExtras().getString("com.parse.Data"));  
                          Log.d(TAG, "got action " + action + " on channel " + channel + " with:");  
                          Iterator itr = json.keys();  
                          while (itr.hasNext()) {  
                               String key = (String) itr.next();  
                               if (key.equals("customdata"))  
                               {  
                                    Intent pupInt = new Intent(context, ShowPopUp.class);  
                                    pupInt.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK );  
                                    context.getApplicationContext().startActivity(pupInt);  
                               }  
                               Log.d(TAG, "..." + key + " => " + json.getString(key));  
                          }  
                     }  
                }  
           } catch (JSONException e) {  
                Log.d(TAG, "JSONException: " + e.getMessage());  
           }  
      }  
 }  

The receiver verify the action that is required matches the registered one and start the ShowPopUp activity. The ShowPopUp activity simply display the message as dialog widow. In order to achieve it flow the steps.

1. There are couple of ways to define popup window in android I choose to create the popup as activity that is launched as dialog. Define layout file popupdialog.xml


 <?xml version="1.0" encoding="utf-8"?>  
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:id="@+id/menuEditItemLayout"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent"  
   android:orientation="vertical" >  
   <LinearLayout  
     android:layout_width="250dp"  
     android:layout_height="wrap_content"  
     android:gravity="center" >  
     <TextView  
       android:id="@+id/durationTitle"  
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:layout_alignParentTop="true"  
       android:layout_gravity="center"  
       android:text="Task"  
       android:textColor="@android:color/holo_blue_dark"  
       android:textSize="25sp" />  
   </LinearLayout>  
   <RelativeLayout  
     android:layout_width="match_parent"  
     android:layout_height="48dp"  
     android:layout_alignParentBottom="true">  
     <View  
       android:layout_width="match_parent"  
       android:layout_height="1dip"  
       android:layout_marginLeft="4dip"  
       android:layout_marginRight="4dip"  
       android:background="?android:attr/dividerVertical"  
       android:layout_alignParentTop="true"/>  
     <View  
       android:id="@+id/ViewColorPickerHelper"  
       android:layout_width="1dip"  
       android:layout_height="wrap_content"  
       android:layout_alignParentTop="true"  
       android:layout_alignParentBottom="true"  
       android:layout_marginBottom="4dip"  
       android:layout_marginTop="4dip"  
       android:background="?android:attr/dividerVertical"   
       android:layout_centerHorizontal="true"/>  
     <Button  
       android:id="@+id/popOkB"  
       android:layout_width="wrap_content"  
       android:layout_height="wrap_content"  
       android:layout_alignParentLeft="true"  
       android:layout_alignParentTop="true"  
       android:layout_toLeftOf="@id/ViewColorPickerHelper"  
       android:background="?android:attr/selectableItemBackground"  
       android:text="@android:string/cancel"   
       android:layout_alignParentBottom="true"/>  
     <Button  
       android:id="@+id/popCancelB"  
       android:layout_width="wrap_content"  
       android:layout_height="match_parent"  
       android:layout_alignParentRight="true"  
       android:layout_alignParentTop="true"  
       android:background="?android:attr/selectableItemBackground"  
       android:text="@android:string/ok"   
       android:layout_alignParentBottom="true"   
       android:layout_toRightOf="@id/ViewColorPickerHelper"/>  
   </RelativeLayout>  
 </LinearLayout>  

2. The activity implementation:

 public class ShowPopUp extends Activity implements OnClickListener {  
      Button ok;  
      Button cancel;  
      boolean click = true;  
      @Override  
      public void onCreate(Bundle savedInstanceState) {  
           super.onCreate(savedInstanceState);  
           setTitle("Cupon");  
           setContentView(R.layout.popupdialog);  
           ok = (Button)findViewById(R.id.popOkB);  
           ok.setOnClickListener(this);  
           cancel = (Button)findViewById(R.id.popCancelB);  
           cancel.setOnClickListener(this);  
      }  
      @Override  
      public void onClick(View arg0) {  
           // TODO Auto-generated method stub  
           finish();  
      }  
 }  

3. Register the activity in the manifest file

 <activity  
      android:name="com.iakremera.pushnotificationdemo.ShowPopUp"  
      android:label="@string/app_name"  
      android:theme="@android:style/Theme.Holo.Light.Dialog" >  
 </activity>  

In order to send the notification you can send the following JSON object (this is only an example).
In order to launch an intent the JSON object should not include the "alert" field.  The notification text should be inserted into  user defined field inside the JSON object (like "customdata").

In the attached source code the notification is sent from the device.
.
 {  
 "action":"com.iakremera.pushnotificationdemo.UPDATE_STATUS",  
 "customdata":"My string"  
 }  

The screenshot :

The source code for this demo project can be downloaded here.
More options using Parse.com push notification can be found here.

Wednesday, May 1, 2013

Android Crash report using ACRA ,BugSense and Parse.com

Android Crash report using ACRA ,BugSense and Parse.com

In my previous post I have described how to integrate ACRA and goolge form in order to get crash reports from Android application. Google as published new version of their form so it can no longer be used as ACRA report storage (more details can be found here). If you want to use ACRA and do not have your own server you need to choose other backends.
I will present two options:
1. Using BugSense.
2. Using Parse.com.

Using BugSense

BugSense provide tools for monitor your mobile application. BugSense has crash report similar to ACRA but BugSense web server can also get ACRA reports without the need to download or use additional SDKs.

In order to integrate ACRA abd BugSense:
1. Open account in BugSense.
2. Define your application in BugSense dashboard
3. Create Android application class and add it to your project Manifest file.

 @ReportsCrashes(formUri = "http://www.bugsense.com/api/acra?api_key=YOUR_API_KEY", formKey="")  
 public class MyApp extends Application  
 {  
  @Override  
  public void onCreate() {  
  // TODO Auto-generated method stub  
  super.onCreate();   
  ACRA.init(this);  
  }  

Using Parse.com

Parse  provides backend services for mobile and web application it also has push notifications services. It free addition provide about 1M requests per month. I will use Parse in order to store ACRA crash reports.

Integrating Parse and ACRA:
1. Register to Parse and download the SDK.
2. Create application in Parse website and get the application,client keys.
3. Add the SDK to you application Lib directory and add it to build path.
4. Verify that you have the following permissions in you Manifest file

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

5. You need to get Application key and Client key for your application (look at Parse application dashboard settings)
6. Create Android application class and add it to your project Manifest file. 
7. Initialize parse sdk, ACRA and register report sender in the application class onCreate method. 

 import org.acra.ACRA;  
 import org.acra.ErrorReporter;  
 import org.acra.annotation.ReportsCrashes;  
 import android.app.Application;  
 import com.parse.Parse;  
 import com.parse.ParseObject;  
    
 @ReportsCrashes( formKey="")  
 public class MyApp extends Application   
 {  
      @Override  
      public void onCreate() {  
           // TODO Auto-generated method stub  
           super.onCreate();  
           Parse.initialize(this, Application key,Client key);   
           ACRA.init(this);  
           ErrorReporter.getInstance().setReportSender(new LocalSender(this));  
      }    
 }  

7. The following code implements ACRA report sender that will send the crash report file to Parse.com. The code uses ParseFile and ParseObject (details).

 import java.io.ByteArrayOutputStream;  

 public class LocalSender implements ReportSender {  
      private final Map<ReportField, String> mMapping = new HashMap<ReportField, String>() ;  
      private FileOutputStream crashReport = null;  
      private Context ctx;  
      public LocalSender(Context ct) {  
           ctx = ct;  
      }  
      public void send(CrashReportData report) throws ReportSenderException {  
           final Map<String, String> finalReport = remap(report);  
           ByteArrayOutputStream buf = new ByteArrayOutputStream();  
           Log.i("hcsh","Report send");  
           try {  
                Set set = finalReport.entrySet();  
                Iterator i = set.iterator();  
                String tmp;  
                while (i.hasNext()) {  
                     Map.Entry<String,String> me = (Map.Entry) i.next();  
                     tmp = "[" + me.getKey() + "]=" + me.getValue();  
                     buf.write(tmp.getBytes());  
                }  
                ParseFile myFile = new ParseFile("crash.txt", buf.toByteArray());  
                myFile.save();  
                ParseObject jobApplication = new ParseObject("AppCrash");  
                jobApplication.put("MyCrash", "Test App");  
                jobApplication.put("applicantResumeFile", myFile);  
                try {  
                     jobApplication.save();  
                } catch (ParseException e) {  
                     // TODO Auto-generated catch block  
                     e.printStackTrace();  
                }  
           }catch (FileNotFoundException e) {  
                Log.e("TAG", "IO ERROR",e);  
           }  
           catch (IOException e) {  
                Log.e("TAG", "IO ERROR",e);  
           } catch (ParseException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
           }  
 }  
 private Map<String, String> remap(Map<ReportField, String> report) {  
      ReportField[] fields = ACRA.getConfig().customReportContent();  
      if (fields.length == 0) {  
           fields = ACRA.DEFAULT_REPORT_FIELDS;  
      }  
      final Map<String, String> finalReport = new HashMap<String, String>(  
                report.size());  
      for (ReportField field : fields) {  
           if (mMapping == null || mMapping.get(field) == null) {  
                finalReport.put(field.toString(), report.get(field));  
           } else {  
                finalReport.put(mMapping.get(field), report.get(field));  
           }  
      }  
      return finalReport;  
 }  
 }  

8. In case of crash the result on Parse dashboard will be :




Each crash will be display in separate row with report time stamp.