I have been rather unhappy with the mechanism that the Google market provides to get user feedback from android apps. Aside from some basic statistics on the total and active numbers of installations, you get reports on uncaught application crashes and freezes – if the user decides to send an error report to Google. These reports are collected by error, and can be viewed through the developer portal on the Android market:
The error reports contain the following information:
– Date/Time of the error
– Version number of your application
– Exception class and method
– Stack trace
– Short user message (optional)
In the past, I’ve found quite a few issues in my code this way, however this only works for errors that cause your application to freeze; but what about issues that you handle yourself, for example by showing an error message? Currently, there is no way to collect these errors, or to fix them. In the case of one of my applications, this turns out to be quite necessary, as it communicates with a number of different server builds. In theory, all of them implement the same protocol (DAAP), but as it turns out, in practice they all have their small differences. Given the number of different implementations, it becomes virtually impossible to test for all of them.
Therefore, I’ve tried to make my app as robust as possible by trying to catch these errors whenever they occur, and to display a simple, non-technical error message to the user:
Recently, I’ve received some comments that the app was not working for some users, but since it no longer crashed, there was no way for me to figure out what was happening. So, I decided to implement my own feedback mechanism for these kinds of “soft” errors. The first time such an error is encountered, I present the user with an new dialog:
Hitting yes will submit the feedback report, while hitting no will just close the dialog as before. The final option, which I think it quite important for some users will turn off this feature altogether (by setting a SharedPreference), and revert to the old error dialog permanently. If the user decides to submit feedback, all the app has to do is to call the following from the current activity:
// this - context // source - Location where the error was detected // exception - optional exception object new Feedback(this, "source", exception).submit();
This invokes the submit method of the Feedback class (full source available):
public void submit() { new Thread() { public void run() { HttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(Constants.FEEDBACK_URL); try { Log.d(Constants.LOG_SOURCE, "Submitting feedback request."); // create post data List params = new ArrayList(2); params.add(new BasicNameValuePair(APP_PARAM, Constants.LOG_SOURCE)); params.add(new BasicNameValuePair(DATA_PARAM, prepareFeedback().toString())); httpPost.setEntity(new UrlEncodedFormEntity(params)); // Execute HTTP Post Request httpClient.execute(httpPost); } catch (Exception ex) { Log.w(Constants.LOG_SOURCE, "Could not submit feedback.", ex); } } }.start(); }
The method makes a post-request to a script on my domain to collect the data. On the server, this data can be stored, processed, etc. More on this later.
The actual data collection happens in prepareFeedback. In here, we collect the same information as found in the native Android error report:
protected JSONObject prepareFeedback() throws Exception { // assemble general information JSONObject root = new JSONObject(); root.put("date", new Date().toGMTString()); root.put("source", _source); root.put("appVersion", _context.getPackageManager(). getPackageInfo(_context.getPackageName(), 0).versionName); // assemble device information JSONObject device = new JSONObject(); device.put("release", Build.VERSION.RELEASE); device.put("sdkVersion", Build.VERSION.SDK_INT); device.put("brand", Build.BRAND); device.put("device", Build.DEVICE); device.put("display", Build.DISPLAY); device.put("manufacturer", Build.MANUFACTURER); device.put("model", Build.MODEL); device.put("product", Build.PRODUCT); device.put("user", Build.USER); root.put("device", device); // assemble exception information JSONObject exception = new JSONObject(); if (exception != null) { exception.put("message", _exception.getMessage()); JSONArray stack = new JSONArray(); StackTraceElement elements[] = _exception.getStackTrace(); for (StackTraceElement el : elements) { stack.put("at " + el.getClassName() + "." + el.getMethodName() + "(" + el.getFileName() + ":" + el.getLineNumber() + ")"); } exception.put("stackTrace", stack); } root.put("exception", exception); return root; }
The feedback class is abstract, with the intention that you can create your own custom feedback class to override the prepareFeedback method. In my case, I add application specific information to the report:
public class CustomFeedback extends Feedback { public CustomFeedback(final Context context, final String source, final Exception ex) { super(context, source, ex); } @Override protected JSONObject prepareFeedback() throws Exception { JSONObject root = super.prepareFeedback(); // add additional properties root.put("action", "..."); return root; } }
For now, the server script just stores the report data in a database. I will provide further details as I complete the implementation.
Hello, nice job on the error reporting. I am looking in to doing something similar as I am not satisfied with the default implementation. I have absolutely no experience with server side scripting. Could you possibly post a generic example of your server script or possibly email it to me. Thanks a bunch!
Hi Mike, check out this sample as a starting point. It takes the data that is posted to the server and stores each log in a separate file. I’m actually using a database for my own apps, but that makes things more complicated. Let me know if you have any questions.