Saturday, October 1, 2011

Android: AsyncTaskLoader Exception Handling

Been months that I dont drop by here, and here on the weekend spending time to code blog...

On a side note: I'm about to enter new chapter of life, things are going to be very exciting and unpredictable in equal measure in the next few months. I'm so much looking forward into that :)

Okay, back to the topic.

While doing the implementation of AsyncTaskLoader, I realized that it doesn't provide a straightforward mechanism to handle exception. I googled it up, and found this thread. Google engineer suggested the following:

As far as errors from loaders, the design is that there are no errors as such. Ultimately they need to deliver a result back to the activity/fragment.  If your result can include an error state, then this should be part of the result you deliver.  This should be as simple as wrapping your result data in a container class that has it and the error state, and making that the result. 

With that in mind, I coded the following wrapper class to wrap the result of the loader:


package com.test;

public class AsyncResult< D > {
	private Exception exception;
	private D data;

	public void setException(Exception exception) {
		this.exception = exception;
	}

	public Exception getException() {
		return exception;
	}

	public void setData(D data) {
		this.data = data;
	}

	public D getData() {
		return data;
	}

}



And the loader class will be implemented like the following:
package com.test;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;

public class MyAsyncTaskLoader extends AsyncTaskLoader< AsyncResult < List < String >>> {
	private AsyncResult< List < String >> data;

	public MyAsyncTaskLoader(Context context) {
		super(context);
	}

	@Override
	public void deliverResult(AsyncResult < List < String>> data) {
		if (isReset()) {
			// a query came in while the loader is stopped
			return;
		}

		this.data = data;

		super.deliverResult(data);
	}

	@Override
	public AsyncResult< List < String >> loadInBackground() {
		AsyncResult< List< String>> result = new AsyncResult< List< String >>();

		List< String> dataList = null;

		try {
			dataList = new ArrayList< String>();

			// load data in background
			// when exception occurs, it should be caught

		} catch (Exception ex) {
			result.setException(ex);
		}

		result.setData(dataList);

		return result;
	}

	@Override
	protected void onStartLoading() {
		if (data != null) {
			deliverResult(data);
		}

		if (takeContentChanged() || data == null) {
			forceLoad();
		}
	}

	@Override
	protected void onStopLoading() {
		cancelLoad();
	}

	@Override
	protected void onReset() {
		super.onReset();

		onStopLoading();

		data = null;
	}
}


And here I handled the exception in onLoadFinished by checking if the exception in the wrapper class is not null.
private final LoaderCallbacks< AsyncResult < List < String >>> loaderCallbacks = new LoaderCallbacks< AsyncResult< List < String >>>() {

		@Override
		public Loader< AsyncResult < List < String>>> onCreateLoader(int id, Bundle args) {
			MyAsyncTaskLoader loader = new MyAsyncTaskLoader(TestActivity.this);
			loader.setUpdateThrottle(1000);

			return loader;
		}

		@Override
		public void onLoadFinished(Loader < AsyncResult < List < String >>> loader, final AsyncResult< List < String >> result) {

			Exception exception = result.getException();
			if (exception != null) {
				Toast.makeText(TestActivity.this, exception.getMessage(), Toast.LENGTH_SHORT).show();
			} else {
				// process the result
			}
		}

		@Override
		public void onLoaderReset(Loader < AsyncResult < List < String >>> loader) {
			loader.reset();
		}
	};


And we are done with the implementation and exception handling!

If you have better implementation, please feel free to give feedback.

 The entire source code could be found in github

5 comments:

  1. Instead of making a lot of if/else statements to check the exception type, you can do try { throw result.getException (); } catch ... instead, especially if you need to handle something hierarchical with specialization.

    ReplyDelete
    Replies
    1. Actually, a built-in result.throwException () might express the pattern better.

      Delete
    2. Here's my complete alternative http://blog.ericwoodruff.me/2013/08/variation-on-asyncresult-for-android.html

      Delete