Monday, April 7, 2014

Android Application case study

A year ago I have uploaded my first application Little Genius to Google play store.
I developed this game for my youngest son.
Little Genius is color / shape matching games for kids at ages 2.5-4 years old. I have developed this game to be kids and parents friendly. There are no advertisements and children can play without parents assistance.
After a year the application has more then 5000 downloads.
I am amazed every time i look at the application statistic page so here are few facts about the downloads statistics:
 - 143 countries and 90 languages
 - Average of 100 users per day.
 - 600 different device models from 90 brands.
 -  1880 network operators.
 -  90 different screen resolutions.
 -  Operating system version: 85% of the downloads are from Ice Cream Sandwich (4.0) release and above.
I promote my application using social networks (Google+ and Facebook).

During the year I have updated my application 7 times adding more levels from 7 at the start to 18 today.

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.

Monday, April 1, 2013

Adding Crash Reports

During the development phase of Android application you can use: Log messages, toast and debugger in order to debug your application.
You can use Android emulator or real device connected to your development environment in order to test your application and get all log messages and unexpected behavioral. 
While testing your application on real devices and especially after successful releasing the application it is very useful to get your application crash report. 
If your application crashes any where in the world it is better to know about it in order to fix it.
A free tool that enable the developer get his application crash report is ACRA
ACRA is very simple library that you add to your application.Crash reports are sent Google drive document and get email notification (please follow the setup steps).
The information receive from ACRA includes:

  • Application name and version
  • Phone brand and model
  • Android version and build number
  • Memory information
  • Device initial configuration
  • Crash configuration
  • Stack trace

More information can be add to the crash report for example logcat output, application log file etc...

Include ACRA in your application in four simple steps
1. Download ACRA library and include it in your application build path.
2. Add application class to the manifest and 
add internet permission. 

1:  <manifest ...>  
2:   <application ... android:name="MyApplication">  
3:    ...  
4:   </application>  
5:   <uses-permission android:name="android.permission.INTERNET">  
6:   </uses-permission>  
7:  </manifest>  

3. Generate Google Doc key (instructions from ACRA). Google drive document can be set to send  email notification on every change in the document. Remark Google has made changes in their Google forms for new option please see my post.

4. Implement application class
1:  import org.acra.*;  
2:  import org.acra.annotation.*;  
//Generate Google Doc code
3:  @ReportsCrashes(formKey = "dGVacG0ydVHnaNHjRjVTUTEtb3FPWGc6MQ")  
4:  public class MyApplication extends Application {  
5:   @Override  
6:   public void onCreate() {  
7:    // The following line triggers the initialization of ACRA  
8:    ACRA.init(this);  
9:    super.onCreate();  
10:   }  
11:  }       

ACRA report sender can be extend/change according to your application requirements by implementing you own report sender, example implementation for saving the crash report to file located on the SD card can be found here.