14

I didn't find any help on this topic. The Docs say

Cursor-based pagination is the most efficient method of paging and should always be used where possible - a cursor refers to a random string of characters which mark a specific item in a list of data. Unless this item is deleted, the cursor will always point to the same part of the list, but it will be invalidated if an item is removed. Therefore, your app shouldn't store any older cursors or assume that they will still be valid.

When reading an edge that supports cursor pagination, you will see the following JSON response:

{
  "data": [
     ... Endpoint data is here
  ],
  "paging": {
    "cursors": {
      "after": "MTAxNTExOTQ1MjAwNzI5NDE=",
      "before": "NDMyNzQyODI3OTQw"
    },
    "previous": "https://graph.facebook.com/me/albums?limit=25&before=NDMyNzQyODI3OTQw"
    "next": "https://graph.facebook.com/me/albums?limit=25&after=MTAxNTExOTQ1MjAwNzI5NDE="
  }
}

I am using this format to make an api call, how can i go through all pages in a loop

/* make the API call */
new GraphRequest(
    session,
    "/{user-id}/statuses",
    null,
    HttpMethod.GET,
    new GraphRequest.Callback() {
        public void onCompleted(GraphResponse response) {
            /* handle the result */
        }
    }
).executeAsync();
Mike Laren
  • 8,028
  • 17
  • 51
  • 70
Web Development
  • 421
  • 1
  • 3
  • 15

5 Answers5

12

I figured out a good way to traverse through facebook graph api pages using cursor pagination

    final String[] afterString = {""};  // will contain the next page cursor
    final Boolean[] noData = {false};   // stop when there is no after cursor 
    do {
        Bundle params = new Bundle();
        params.putString("after", afterString[0]);
        new GraphRequest(
                accessToken,
                personId + "/likes",
                params,
                HttpMethod.GET,
                new GraphRequest.Callback() {
                    @Override
                    public void onCompleted(GraphResponse graphResponse) {
                        JSONObject jsonObject = graphResponse.getJSONObject(); 
                        try {
                            JSONArray jsonArray = jsonObject.getJSONArray("data");

                            //  your code 


                            if(!jsonObject.isNull("paging")) {
                                JSONObject paging = jsonObject.getJSONObject("paging");
                                JSONObject cursors = paging.getJSONObject("cursors");
                                if (!cursors.isNull("after"))
                                    afterString[0] = cursors.getString("after");
                                else
                                    noData[0] = true;
                            }
                            else
                                noData[0] = true;
                        } catch (JSONException e) {
                            e.printStackTrace(); 
                        }
                    }
                }
        ).executeAndWait();
    }
    while(!noData[0] == true);
Web Development
  • 421
  • 1
  • 3
  • 15
  • For some reason when I use this code, it just gets the data twice. Not sure why. – Jack Mar 11 '16 at 12:02
  • this is not working. I'm getting same data in infinite loop. https://stackoverflow.com/questions/45319372/facebook-graph-api-cursor-based-paging-not-working-in-android – Harshal Bhatt Aug 19 '17 at 06:07
11

Do not reinvent the wheel.

GraphResponse class already has a convenient method for paging. GraphResponse.getRequestForPagedResults() returns GraphRequest object, and you can use that object for paging.

Also I found code snippet from facebook-android-sdk's unit test code.

GraphRequest nextRequest = response.getRequestForPagedResults(GraphResponse.PagingDirection.NEXT);
nextRequest.setCallback(request.getCallback());
response = nextRequest.executeAndWait();
김준호
  • 15,997
  • 9
  • 44
  • 38
  • this does not work with sub-objects like me/?groups, only top level. instead, it is possible to do newGraphPathRequest to me/groups – Shimon Doodkin Aug 27 '16 at 08:03
  • I'm getting `android.os.NetworkOnMainThreadException` on using this method. Please see the question here: https://stackoverflow.com/q/45875090/6144372 – Hammad Nasir Aug 25 '17 at 05:46
5

Though it's true that you should use GraphResponse.getRequestForPagedResults(), you can't use executeAndWait() unless you run it in a different thread.

You can make it even easier using executeAsync().

To get the first set of results:

    new GraphRequest(AccessToken.getCurrentAccessToken(),
            "/" + facebookID + "/invitable_friends",
            null,
            HttpMethod.GET,
            new GraphRequest.Callback() {
                public void onCompleted(GraphResponse response) {
                    //your code

                    //save the last GraphResponse you received
                    lastGraphResponse = response;
                }
            }
    ).executeAsync();

Use that lastGraphResponse to get the next set of results:

    GraphRequest nextResultsRequests = lastGraphResponse.getRequestForPagedResults(GraphResponse.PagingDirection.NEXT);
    if (nextResultsRequests != null) {
        nextResultsRequests.setCallback(new GraphRequest.Callback() {
            @Override
            public void onCompleted(GraphResponse response) {
                //your code

                //save the last GraphResponse you received
                lastGraphResponse = response;
            }
        });
        nextResultsRequests.executeAsync();
    }

You can merge all that in a single method!

RominaV
  • 3,335
  • 1
  • 29
  • 59
3

I use this code:

final String[] afterString = {""}; 
final Boolean[] noData = {true};
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);

do{
   GraphRequest request = GraphRequest.newGraphPathRequest(
            AccessToken.getCurrentAccessToken(),
            "/me/likes",
            new GraphRequest.Callback() {
                @Override
                public void onCompleted(GraphResponse response) {
                    // Insert your code here
                    JSONObject jsonObject = response.getJSONObject();
                    try{
                        if(jsonObject.length() > 1) {

                            JSONObject jsonFacebook = (JSONObject) new JSONTokener(jsonObject.toString()).nextValue();
                            JSONObject likes_paging = (JSONObject) new JSONTokener(jsonFacebook.getJSONObject("paging").toString()).nextValue();

                            ArrayList<String> likes = new ArrayList<String>();
                            for (int i = 0; i < jsonFacebook.getJSONArray("data").length(); i++) {
                                likes.add(jsonFacebook.getJSONArray("data").getJSONObject(i).getString("name"));
                            }
                            afterString[0] = (String) likes_paging.getJSONObject("cursors").get("after");

                        }else{
                            noData[0] = false;
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            });

    Bundle parameters = new Bundle();
    parameters.putString("pretty", "0");
    parameters.putString("limit", "100");
    parameters.putString("after", afterString[0]);
    request.setParameters(parameters);
    request.executeAndWait();
}while(noData[0] == true);
  • 1
    By setting the thread policy above to permitAll you are allowing android to perform the call on the main ui thread. Why not execute the above in a different thread. According to facebooks documentation it says => executeAndWait: "Executes this request on the current thread and blocks while waiting for the response. This should only be called if you have transitioned off the UI thread." – Aaron Dancygier Jan 20 '16 at 21:04
0

What I did in my project is basically doing the first call in onSearchClicked() and getting result in onCompleted() callback in ViewModel. If nextRequest is null, I return the list of results back to my fragment, if not, I do the next call, until the end is reached (nextRequest == null). Then, the accumulated list is returned to the fragment.

class SearchViewModel() : GraphRequest.Callback {

    private var allPlaces = arrayListOf<FacebookPlace>()

    fun onSearchClicked(location: Location) {
      val request = GraphRequest.newGraphPathRequest(accessToken, "/search", this)
      request.parameters = parameters
      request.executeAsync()
    }

    override fun onCompleted(response: GraphResponse?) {
      if (response != null) {
        allPlaces.addAll(response.data)

        val nextRequest = response.getRequestForPagedResults(GraphResponse.PagingDirection.NEXT)
        if (nextRequest == null) {
            uiEvent.value = ShowPlaces(allPlaces)
        } else {
            nextRequest.callback = this
            nextRequest.executeAsync()
        }
    } else {
        Log.e(TAG, "Search response is null.")
    }
}
lomza
  • 9,412
  • 15
  • 70
  • 85