Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save natuanorg/78734ea9b3e8ba62588e7e386f0a6121 to your computer and use it in GitHub Desktop.
Save natuanorg/78734ea9b3e8ba62588e7e386f0a6121 to your computer and use it in GitHub Desktop.
AsyncTaskLoader Sample
/**
* <p>
* This loader caches the result so that it can be taken later - for example, after configuration
* was changed and activity is re-created.
* </p>
* Created by hiroshi on 2014/12/03.
*/
public abstract class CachedAsyncTaskLoader<T> extends AsyncTaskLoader<T> {
private T mCached;
private Throwable mError;
private LoaderListener<T> mListener;
public CachedAsyncTaskLoader(Context context) {
super(context);
this.init();
}
private void init() {
this.mError = null;
}
public void setListener(LoaderListener<T> listener) {
this.mListener = listener;
}
/**
* Called when there is new data to deliver to the client. The
* super class will take care of delivering it; the implementation
* here just adds a little more logic.
*/
@Override
public void deliverResult(T data) {
// If the loader is reset and it is going to finish, purge the cached data
if (this.isReset()) {
if (this.mCached != null) {
this.mCached = null;
}
return;
}
this.mCached = data;
if (this.isStarted()) {
super.deliverResult(data);
}
}
/**
* Handles a request to start the Loader.
*/
@Override
protected void onStartLoading() {
// Return the cached data if exists
if (this.mCached != null) {
deliverResult(this.mCached);
return;
}
// If data source is changed or cached data is null, try to get data
if (this.takeContentChanged() || this.mCached == null) {
this.forceLoad();
}
}
/**
* Handles a request to stop the Loader.
*/
@Override
protected void onStopLoading() {
this.cancelLoad();
}
/**
* Handles a request to completely reset the Loader.
*/
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
this.onStopLoading();
// Initialize status and delete the cached data so that, in the next time, new data is fetched
this.init();
this.mCached = null;
}
@Override
public T loadInBackground() {
if (this.mListener != null) {
this.mListener.onLoadStarted(this);
}
T data = null;
try {
data = this.load();
} catch (Exception e) {
this.mError = e;
}
if (this.mListener != null) {
this.mListener.onLoadFinished(this, data);
}
return data;
}
abstract protected T load();
public void refresh() {
this.reset();
this.startLoading();
}
public Throwable getError() {
return this.mError;
}
public boolean hasError() {
return this.mError != null;
}
public static interface LoaderListener<T> {
public void onLoadStarted(final CachedAsyncTaskLoader<T> loader);
public void onLoadFinished(final CachedAsyncTaskLoader<T> loader, T data);
}
}
/**
* Async task loader to fetch articles page by page.
* Created by hiroshi on 1/2/15.
*/
public class PagingAsyncTaskLoader extends AsyncTaskLoader<ArticlesPage> {
private static final int NUM_ARTICLES_PER_PAGE = 20;
private boolean mIsLoading;
private boolean mHasError;
private boolean mHasMoreResults;
private ArticlesPage mPage;
public PagingAsyncTaskLoader(Context context) {
super(context);
this.init();
}
private void init() {
this.mPage = new ArticlesPage(null, 0);
this.mHasError = false;
this.mHasMoreResults = true;
this.setLoading(true);
}
/**
* Called on a worker thread to perform the actual load and to return
* the result of the load operation.
* <p/>
* Implementations should not deliver the result directly, but should return them
* from this method, which will eventually end up calling {@link #deliverResult} on
* the UI thread. If implementations need to process the results on the UI thread
* they may override {@link #deliverResult} and do so there.
* <p/>
* To support cancellation, this method should periodically check the value of
* {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
* Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
* directly instead of polling {@link #isLoadInBackgroundCanceled}.
* <p/>
* When the load is canceled, this method may either return normally or throw
* {@link android.os.OperationCanceledException}. In either case, the {@link android.content.Loader} will
* call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
* result object, if any.
*
* @return The result of the load operation.
* @throws android.os.OperationCanceledException if the load is canceled during execution.
* @see #isLoadInBackgroundCanceled
* @see #cancelLoadInBackground
* @see #onCanceled
*/
@Override
public ArticlesPage loadInBackground() {
this.setLoading(true);
if (this.hasMoreResults()) {
try {
final ArticlesResponse response = service.list(this.mPage.getNextToken(), NUM_ARTICLES_PER_PAGE);
if (response != null && response.getArticles() != null) {
List<Article> articles = response.getArticles();
if (articles.isEmpty()) {
// Empty list indicates there is no more articles
this.mHasMoreResults = false;
return null;
} else {
return new ArticlesPage(articles, response.getNextToken());
}
}
} catch (Exception e) {
// just logs error
Timber.w(e, "Failed to load the articles.");
}
this.mHasError = true;
this.mHasMoreResults = false;
} else {
Timber.d("No following articles. Do nothing.");
}
return null;
}
@Override
public void deliverResult(ArticlesPage data) {
this.setLoading(false);
if (data != null) {
if (this.mPage.getArticles() == null) {
this.mPage.setArticles(data.getArticles());
} else {
this.mPage.getArticles().addAll(data.getArticles());
}
this.mPage.setNextToken(data.getNextToken());
}
if (this.isStarted()) {
super.deliverResult(new ArticlesPage(this.mPage));
}
}
@Override
protected void onStartLoading() {
if (this.mPage.getArticles() != null) {
// If we already have results and are starting up, deliver what we already have.
this.deliverResult(null);
} else {
this.forceLoad();
}
}
@Override
protected void onStopLoading() {
this.setLoading(false);
this.cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
this.onStopLoading();
this.init();
this.mPage.setArticles(null);
}
public boolean hasError() {
return this.mHasError;
}
public boolean hasMoreResults() {
return this.mHasMoreResults;
}
public boolean isLoading() {
return this.mIsLoading;
}
private void setLoading(final boolean loading) {
this.mIsLoading = loading;
}
public void refresh() {
this.reset();
this.startLoading();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment