com.facebook.GraphObjectPagingLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.GraphObjectPagingLoader.java

Source

/**
 * 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;

import android.content.Context;
import android.os.Handler;
import android.support.v4.content.Loader;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

class GraphObjectPagingLoader<T extends GraphObject> extends Loader<SimpleGraphObjectCursor<T>> {
    private final Class<T> graphObjectClass;
    private boolean skipRoundtripIfCached;
    private Request originalRequest;
    private Request currentRequest;
    private String nextLink;
    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() {
        nextLink = 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 (nextLink != null) {
            appendResults = true;
            currentRequest = Request.newGraphPathRequest(originalRequest.getSession(), null,
                    new Request.Callback() {
                        @Override
                        public void onCompleted(Response response) {
                            requestCompleted(response);
                        }
                    });

            // We rely on the "next" link returned to us being in the right format to return the results we expect.
            HttpURLConnection connection = null;
            try {
                connection = Request.createConnection(new URL(nextLink));
            } catch (IOException e) {
                if (onErrorListener != null) {
                    onErrorListener.onError(new FacebookException(e), this);
                }
                return;
            }

            loading = true;
            RequestBatch batch = putRequestIntoBatch(currentRequest, skipRoundtripIfCached);
            batch.setCacheKey(nextLink.toString());
            Request.executeConnectionAsync(connection, 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;
        nextLink = 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 RequestBatch putRequestIntoBatch(Request request, boolean skipRoundtripIfCached) {
        // We just use the request URL as the cache key.
        RequestBatch batch = new RequestBatch(request);
        try {
            batch.setCacheKey(request.getUrlForSingleRequest().toString());
        } catch (MalformedURLException e) {
            throw new FacebookException(e);
        }
        batch.setForceRoundTrip(!skipRoundtripIfCached);
        return batch;
    }

    private void requestCompleted(Response response) {
        Request request = response.getRequest();
        if (request != currentRequest) {
            return;
        }

        loading = false;
        currentRequest = null;

        FacebookException error = response.getError();
        PagedResults result = response.getGraphObjectAs(PagedResults.class);
        if (result == null && error == null) {
            error = new FacebookException("GraphObjectPagingLoader received neither a result nor an error.");
        }

        if (error != null) {
            nextLink = null;

            if (onErrorListener != null) {
                onErrorListener.onError(error, this);
            }
        } else {
            boolean fromCache = response.getIsFromCache();
            addResults(result, fromCache);
            // Once we get any set of results NOT from the cache, stop trying to get any future ones
            // from it.
            if (!fromCache) {
                skipRoundtripIfCached = false;
            }
        }
    }

    private void addResults(PagedResults result, boolean fromCache) {
        SimpleGraphObjectCursor<T> cursorToModify = (cursor == null || !appendResults)
                ? new SimpleGraphObjectCursor<T>()
                : new SimpleGraphObjectCursor<T>(cursor);

        GraphObjectList<T> data = result.getData().castToListOf(graphObjectClass);
        boolean haveData = data.size() > 0;

        if (haveData) {
            PagingInfo paging = result.getPaging();
            if (nextLink != null && nextLink.equals(paging.getNext())) {
                // We got the same "next" link as we just tried to retrieve. This could happen if cached
                // data is invalid. All we can do in this case is pretend we have finished.
                haveData = false;
            } else {
                nextLink = paging.getNext();

                cursorToModify.addGraphObjects(data, fromCache);
                cursorToModify.setMoreObjectsAvailable(true);
            }
        }
        if (!haveData) {
            cursorToModify.setMoreObjectsAvailable(false);
            cursorToModify.setFromCache(fromCache);

            nextLink = null;
        }

        deliverResult(cursorToModify);
    }

    interface PagingInfo extends GraphObject {
        String getNext();
    }

    interface PagedResults extends GraphObject {
        GraphObjectList<GraphObject> getData();

        PagingInfo getPaging();
    }
}