Android 101: Error handling and reporting

Previous:Working with Google Buzz API
Next:Using Google Analytics in your Android application

This article is part of an Android Development Guide. The guide focuses on several subjects necessary to build a fully functional basic app, talking about several features that were introduced in Honeycomb and are still valid for Ice Cream Sandwich. We use a Google Buzz client called Honeybuzz as an example for each topic. Refer to the introduction for a complete list of all articles.

Error handling and reporting is an important aspect of an application because not only will you be aware of the more troublesome areas of your application, but you will also be able to provide a graceful experience in the event that your application crashes.

A very popular library for handling exceptions in Android is ACRA. ACRA will provide you with detailed crash reports and will store everything in a Google Doc form (or using a custom server script or even an email - but a Google Doc is a very handy and quick-to-setup solution).

Furthermore ACRA is customizable in the way that it is presented to the user: you can either have silent errors and background reporting or, on the other hand, provide the user with the option of sending an error report with a short description.

Starting with Froyo, crash reports are sent to the developer using the Android Market (if a user chooses to send them), but I find that ACRA is more customizable and provides accurate reports.

Setting up a Google Spreadsheet for ACRA reporting

You can read a detailed ACRA setup guide on the Google project site. I will just briefly describe the procedure with a few notes of my own.

  • Download the latest version from the ACRA site.
  • Go to Google Docs and import the template CrashReports-template.csv. Make sure conversion is enabled.
  • Open the new document and go to Tools > Form > Create a form.
  • Copy the formKey value that is visible at the bottom of your form window.
  • Type something in the description so that you can save the form.
  • If you want to receive an email notification each time an error report gets saved (or daily for example), you can edit the notification rules.
    • On the old Google Docs interface you expand the Share dropdown and you will see the option there.
    • On the new Google Docs interface you go to Tools > Notification rules...
    • To have a better display of the error messages I advise you to resize the width of the columns and change the alignment as such:

Adding ACRA to your project

Add the ACRA library to your project. Follow the same instructions provided in the article Android 101: Development environment and project creation (see the part about adding external libraries).

In the most basic setup, you only need to specify an attribute in your Application class with the form key of your Google Spreadsheet. For this application, I went a little forward and specified a different set of options:

@ReportsCrashes(formKey="your form key",
                mode = ReportingInteractionMode.NOTIFICATION,
                resToastText = R.string.crash_toast_text, // optional, displayed as soon as the crash occurs, before collecting data which can take a few seconds
                resNotifTickerText = R.string.crash_notif_ticker_text,
                resNotifTitle = R.string.crash_notif_title,
                resNotifText = R.string.crash_notif_text,
                resNotifIcon = android.R.drawable.stat_notify_error, // optional. default is a warning sign
                resDialogText = R.string.crash_dialog_text,
                resDialogIcon = android.R.drawable.ic_dialog_info, //optional. default is a warning sign
                resDialogTitle = R.string.crash_dialog_title, // optional. default is your application name
                resDialogCommentPrompt = R.string.crash_dialog_comment_prompt, // optional. when defined, adds a user text field input with this text resource as a label
                resDialogOkToast = R.string.crash_dialog_ok_toast, // optional. displays a Toast message when the user accepts to send a report.
                sharedPreferencesName = HoneybuzzApplication.PREFERENCES_NAME, // Name of the SharedPreferences that will host the acra.enable or acra.disable preference.
                sharedPreferencesMode = HoneybuzzApplication.PREFERENCES_MODE // The mode that you need for the SharedPreference file creation: Context.MODE_PRIVATE, Context.MODE_WORLD_READABLE or Context.MODE_WORLD_WRITEABLE.
                )
public class HoneybuzzApplication extends Application {
    public static final String PREFERENCES_NAME = "HoneybuzzPrefs";
    public static final int PREFERENCES_MODE = MODE_PRIVATE;
 
    @Override
    public void onCreate() {
        ACRA.init(this); // error reporting
        Logging.setLoggingLevel(); // logging level
        PreferenceManager.setDefaultValues(this, PREFERENCES_NAME, PREFERENCES_MODE, R.xml.preferences, false); // set default preferences
        super.onCreate();
    }
}

Simply specifying the interaction mode for the error reporting, some custom strings and icons for the text in the dialogs and the preferences name so the users’ preferences get stored.

There are three types of interaction in ACRA:

  • SILENT: errors are sent without notifying users and the default crash report screen from Android is displayed (in Froyo or above).
  • TOAST: a simple toast message is displayed to the user and the report is sent without confirmation.
  • NOTIFICATION: a notification message pops up and the user has to explicitly send the error reports, with the option of specifying additional information.

Another nice feature is that your users can specify in the preferences of your application how they want the error reporting to occur.

Custom Logging class

I ended up creating a custom class, analogous to the android.utils.Log class, to aggregate all the logging functionality: not only to write ACRA reports, but also to write to Android's logs (retrievable with the LogCat). This class is used throughout the application to log information messages as well as exceptions and errors.

Depending on the logging level configured, reports will be reported to ACRA.

package com.quasibit.honeybuzz;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.acra.ErrorReporter;

import android.util.Log;

public class Logging {
    private static Level LOGGING_LEVEL = Level.ALL;
    private static final String TAG = "Honeybuzz";
    
    public static void setLoggingLevel() {
        Logger.getLogger("com.google.api.client").setLevel(LOGGING_LEVEL);
    }
    
    public static int e(String tag, String msg, Throwable tr) {
        acraReport(Level.SEVERE, tag, msg, tr);
        
        return Log.e(tag, msg, tr);
    }
    
    public static int e(String msg, Throwable tr) {
        return e(TAG, msg, tr);
    }
    
    public static int e(Throwable tr) {
        return e(TAG, tr.getMessage(), tr);
    }
    
    public static int w(String tag, String msg, Throwable tr) {
        acraReport(Level.WARNING, tag, msg, tr);
        
        return Log.w(tag, msg, tr);
    }
    
    public static int w(String msg, Throwable tr) {
        return w(TAG, msg, tr);
    }
    
    public static int w(String tag, String msg) {
        acraReport(Level.WARNING, tag, msg);
        
        return Log.w(tag, msg);
    }
    
    public static int w(String msg) {
        return w(TAG, msg);
    }
    
    public static int i(String tag, String msg, Throwable tr) {
        acraReport(Level.INFO, tag, msg, tr);
        
        return Log.i(tag, msg, tr);
    }
    
    public static int i(String msg, Throwable tr) {
        return i(TAG, msg, tr);
    }
    
    public static int i(String tag, String msg) {
        acraReport(Level.INFO, tag, msg);
        
        return Log.i(tag, msg);
    }
    
    public static int i(String msg) {
        return i(TAG, msg);
    }
    
    public static int d(String tag, String msg) {
        acraReport(Level.FINEST, tag, msg);
        
        return Log.d(tag, msg);
    }
    
    public static int d(String msg) {
        return d(TAG, msg);
    }

    public static int v(String tag, String msg, Throwable tr) {
        acraReport(Level.FINE, tag, msg, tr);
        
        return Log.v(tag, msg, tr);
    }
    
    public static int v(String msg, Throwable tr) {
        return v(TAG, msg, tr);
    }
    
    public static int v(String tag, String msg) {
        acraReport(Level.FINE, tag, msg);
        
        return Log.v(tag, msg);
    }
    
    public static int v(String msg) {
        return v(TAG, msg);
    }

    private static void acraReport(Level level, String tag, String msg, Throwable tr) {
        if (level.intValue() >= LOGGING_LEVEL.intValue()) {
            acraReport(level, tag, msg);
            // report exception to acra silently
            ErrorReporter.getInstance().handleSilentException(tr);
        }
    }
    
    private static void acraReport(Level level, String tag, String msg) {
        if (level.intValue() >= LOGGING_LEVEL.intValue()) {
            ErrorReporter.getInstance().putCustomData(level.getName() + ": " + tag, msg);
        }
    }
}

Analyzing ACRA reports

Google Spreadsheets doesn’t exactly provide a user friendly way of reading error reports. There are applications that are being built to make use of the error reports provided by ACRA. You can see a few examples in the contributions page of the ACRA site.

BugSense, for example, works well with ACRA. I haven't tried it yet, but it seems to have some great features and only requires minimal configuration.

Previous:Working with Google Buzz API
Next:Using Google Analytics in your Android application