/** * Copyright 2012 Facebook * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.widget; import android.content.Context; import android.os.Handler; import android.support.v4.content.Loader; import com.facebook.*; import com.facebook.model.GraphObject; import com.facebook.model.GraphObjectList; import com.facebook.internal.CacheableRequestBatch; class GraphObjectPagingLoader<T extends GraphObject> extends Loader<SimpleGraphObjectCursor<T>> { private final Class<T> graphObjectClass; private boolean skipRoundtripIfCached; private Request originalRequest; private Request currentRequest; private Request nextRequest; private OnErrorListener onErrorListener; private SimpleGraphObjectCursor<T> cursor; private boolean appendResults = false; private boolean loading = false; public interface OnErrorListener { public void onError(FacebookException error, GraphObjectPagingLoader<?> loader); } public GraphObjectPagingLoader(Context context, Class<T> graphObjectClass) { super(context); this.graphObjectClass = graphObjectClass; } public OnErrorListener getOnErrorListener() { return onErrorListener; } public void setOnErrorListener(OnErrorListener listener) { this.onErrorListener = listener; } public SimpleGraphObjectCursor<T> getCursor() { return cursor; } public void clearResults() { nextRequest = null; originalRequest = null; currentRequest = null; deliverResult(null); } public boolean isLoading() { return loading; } public void startLoading(Request request, boolean skipRoundtripIfCached) { originalRequest = request; startLoading(request, skipRoundtripIfCached, 0); } public void refreshOriginalRequest(long afterDelay) { if (originalRequest == null) { throw new FacebookException( "refreshOriginalRequest may not be called until after startLoading has been called."); } startLoading(originalRequest, false, afterDelay); } public void followNextLink() { if (nextRequest != null) { appendResults = true; currentRequest = nextRequest; currentRequest.setCallback(new Request.Callback() { @Override public void onCompleted(Response response) { requestCompleted(response); } }); loading = true; CacheableRequestBatch batch = putRequestIntoBatch(currentRequest, skipRoundtripIfCached); Request.executeBatchAsync(batch); } } @Override public void deliverResult(SimpleGraphObjectCursor<T> cursor) { SimpleGraphObjectCursor<T> oldCursor = this.cursor; this.cursor = cursor; if (isStarted()) { super.deliverResult(cursor); if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) { oldCursor.close(); } } } @Override protected void onStartLoading() { super.onStartLoading(); if (cursor != null) { deliverResult(cursor); } } private void startLoading(Request request, boolean skipRoundtripIfCached, long afterDelay) { this.skipRoundtripIfCached = skipRoundtripIfCached; appendResults = false; nextRequest = null; currentRequest = request; currentRequest.setCallback(new Request.Callback() { @Override public void onCompleted(Response response) { requestCompleted(response); } }); // We are considered loading even if we have a delay. loading = true; final RequestBatch batch = putRequestIntoBatch(request, skipRoundtripIfCached); Runnable r = new Runnable() { @Override public void run() { Request.executeBatchAsync(batch); } }; if (afterDelay == 0) { r.run(); } else { Handler handler = new Handler(); handler.postDelayed(r, afterDelay); } } private CacheableRequestBatch putRequestIntoBatch(Request request, boolean skipRoundtripIfCached) { // We just use the request URL as the cache key. CacheableRequestBatch batch = new CacheableRequestBatch(request); // We use the default cache key (request URL). batch.setForceRoundTrip(!skipRoundtripIfCached); return batch; } private void requestCompleted(Response response) { Request request = response.getRequest(); if (request != currentRequest) { return; } loading = false; currentRequest = null; FacebookRequestError requestError = response.getError(); FacebookException exception = (requestError == null) ? null : requestError.getException(); if (response.getGraphObject() == null && exception == null) { exception = new FacebookException("GraphObjectPagingLoader received neither a result nor an error."); } if (exception != null) { nextRequest = null; if (onErrorListener != null) { onErrorListener.onError(exception, this); } } else { addResults(response); } } private void addResults(Response response) { SimpleGraphObjectCursor<T> cursorToModify = (cursor == null || !appendResults) ? new SimpleGraphObjectCursor<T>() : new SimpleGraphObjectCursor<T>(cursor); PagedResults result = response.getGraphObjectAs(PagedResults.class); boolean fromCache = response.getIsFromCache(); GraphObjectList<T> data = result.getData().castToListOf(graphObjectClass); boolean haveData = data.size() > 0; if (haveData) { nextRequest = response.getRequestForPagedResults(Response.PagingDirection.NEXT); cursorToModify.addGraphObjects(data, fromCache); cursorToModify.setMoreObjectsAvailable(true); } if (!haveData) { cursorToModify.setMoreObjectsAvailable(false); cursorToModify.setFromCache(fromCache); nextRequest = null; } // Once we get any set of results NOT from the cache, stop trying to get any future ones // from it. if (!fromCache) { skipRoundtripIfCached = false; } deliverResult(cursorToModify); } interface PagedResults extends GraphObject { GraphObjectList<GraphObject> getData(); } }