I have a method in my app that allows the user to "like" a post in his/her news feed. It's done with a simple graph request using HttpMethod.POST. But when I try to do an "unlike" action using HttpMethod.DELETE, I get an error callback:
02-08 00:35:57.298: I/Detail(2628): {Response: responseCode: 403, graphObject: null, error:
{HttpStatus: 403, errorCode: 200, errorType: OAuthException, errorMessage: (#200)
Feed story publishing to other users is disabled for this application}, isFromCache:false}
Now I assume this has something to do with the latest attempt to make all apps that integrate with Facebook use all Facebook looking dialogs and styles, but I could be wrong. Here's the roadmap post that has me suspicious:
Removing ability to post to friends walls via Graph API We will
remove the ability to post to a user's friends' walls via the Graph
API. Specifically, posts against [user_id]/feed where [user_id] is
different from the session user, or stream.publish calls where the
target_id user is different from the session user, will fail. If you
want to allow people to post to their friends' timelines, invoke the
feed dialog. Stories that include friends via user mentions tagging or
action tagging will show up on the friend’s timeline (assuming the
friend approves the tag). For more info, see this blog post.
Any ideas on what I could be doing wrong, or is Facebook just ruining me? Thanks!
EDIT: Here's the code I'm using to run the request.
Request likeRequest = new Request(Utility.fbSession, null, null, null, new Request.Callback() {
#Override
public void onCompleted(Response response) {
String responseString = response.toString();
Log.i("Detail", responseString);
updateDetail();
}
});
HttpMethod nextLikeCall = HttpMethod.DELETE;
likeRequest.setHttpMethod(nextLikeCall);
likeRequest.setGraphPath(itemId+"/likes");
likeRequest.executeAsync();
When you grab the post id from the graph data, it should be in a format like: XXXXX_YYYYY. The XXXXX is simply the users id and YYYYY is the actual post id. What you need to do is extract and use just the YYYYY portion of the post id that graph gives you. so instead of graph.facebook.com/XXXXX_YYYYY/likes.... you want to send graph.facebook.com/YYYYY/likes. This will work with both liking and unliking, you can test in graph explorer first before hacking together a substring extraction method.
Not sure how to extract a section of a string on Android, but I know in Objective-C/iOS, it can be done like this (code not tested, for reference/idea) :
SString *actualPostIdStr; //The String we will put the actual postId in
NSString *oldIdStr = //<the string in format XXXXX_YYYYY>
NSInteger charCount = [oldIdStr length]; //get the length of the original XXXXX_YYYYY string
NSRange fRangeCount = [oldIdStr rangeOfString:#"_"]; //get count of characters to remove (XXXXXX)
if (fRangeCount.location != NSNotFound){
NSInteger startingPos = fRangeCount.location + 1; //get the starting character position of the actual postId
actualPostIdStr = [[oldIdStr substringWithRange:NSMakeRange(startingPos, charCount - startingPos)] copy];
}
Hope this helps.
EDIT:
Ok, so I have been playing around with likes all day... It seems that this method sometimes doesn't work, but it all depends on the type of graph object which you are attempting to like/unlike. For example... plain status posts, this method works perfectly. However, I ran into a problem when trying to like a photo with a message/story post object. It turns out, in the graph data for this type of post object with photo, there is an additional paramter called "object_id", in addition to the plain "id" that is in status post graph data. in this case, with the photo and story post, you need to pass the "object_id", unaltered for successful unliking.
This mess seems like either a bug on FB's end, or they are making changes and disallowing likes/unlikes from graph api & just forgot/haven't to told us yet :) hopefully the former. In the mean time, you're just going to have to use my above answer, but just make sure you test with as many different types of post objects you can find, and use if conditions when a different id (portion of "id"... "object_id"... etc) is required.
Sometimes Daniel McCarthy's method don't work. In this case we need get Graph API request XXX_YYYY, find field object_id and unlike this object_id fid.
Related
I have an Android application in which I'm using Azure AD B2C to authenticate users. Users login and logout of the application as needed. I would like to give the user the option to delete their own account.
I understand that I need to use the Azure AD Graph API to delete the user. This is what I have so far:
According to this link, it looks like deleting a user from a personal account (which is what the B2C users are using) is not possible. Is that correct?
Here's my code snippet for the Graph API call. Feel free to ignore it if I'm off track and there is a better way to solve this.
I believe I need a separate access token than what my app currently has (as the graph API requires other API consent). So, I'm getting the access token as follows:
AcquireTokenParameters parameters = new AcquireTokenParameters.Builder()
.startAuthorizationFromActivity(getActivity())
.fromAuthority(B2CConfiguration.getAuthorityFromPolicyName(B2CConfiguration.Policies.get("SignUpSignIn")))
.withScopes(B2CConfiguration.getGraphAPIScopes())
.withPrompt(Prompt.CONSENT)
.withCallback(getGraphAPIAuthCallback())
.build();
taxApp.acquireToken(parameters);
In the getGraphAPIAuthCallback() method, I'm calling the Graph API using a separate thread (in the background):
boolean resp = new DeleteUser().execute(authenticationResult.getAccessToken()).get();
Finally, in my DeleterUser() AsyncTask, I'm doing the following:
#Override
protected Boolean doInBackground(String... aToken) {
final String asToken = aToken[0];
//this method will be running on background thread so don't update UI from here
//do your long running http tasks here,you dont want to pass argument and u can access the parent class' variable url over here
IAuthenticationProvider mAuthenticationProvider = new IAuthenticationProvider() {
#Override
public void authenticateRequest(final IHttpRequest request) {
request.addHeader("Authorization",
"Bearer " + asToken);
}
};
final IClientConfig mClientConfig = DefaultClientConfig
.createWithAuthenticationProvider(mAuthenticationProvider);
final IGraphServiceClient graphClient = new GraphServiceClient.Builder()
.fromConfig(mClientConfig)
.buildClient();
try {
graphClient.getMe().buildRequest().delete();
} catch (Exception e) {
Log.d(AccountSettingFragment.class.toString(), "Error deleting user. Error Details: " + e.getStackTrace());
}
return true;
}
Currently, my app fails when trying to get an access token with a null pointer exception:
com.microsoft.identity.client.exception.MsalClientException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
Any idea what I need to do to provide the user the option to users to delete their own account? Thank you!
Thanks for the help, #allen-wu. Due to his help, this azure feedback request and this azure doc, I was able to figure out how to get and delete users silently (without needing intervention).
As #allen-wu stated, you cannot have a user delete itself. So, I decided to have the mobile app call my server-side NodeJS API when the user clicks the 'Delete Account' button (as I do not want to store the client secret in the android app) and have the NodeJS API call the Azure AD endpoint to delete the user silently. The one caveat is that admin consent is needed the first time you try to auth. Also, I have only tested this for Graph API. I'm not a 100% sure if it works for other APIs as well.
Here are the steps:
Create your application in your AAD B2C tenant. Create a client secret and give it the following API permissions: Directory.ReadWrite.All ;
AuditLog.Read.All (I'm not a 100% sure if we need the AuditLog permission. I haven't tested without it yet).
In a browser, paste the following link:
GET https://login.microsoftonline.com/{tenant}/adminconsent?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&state=12345
&redirect_uri=http://localhost/myapp/permissions
Login using an existing admin account and provide the consent to the app.
Once you've given admin consent, you do not have to repeat steps 1-3 again. Next, make the following call to get an access token:
POST https://login.microsoftonline.com/{B2c_tenant_name}.onmicrosoft.com/oauth2/v2.0/token
In the body, include your client_id, client_secret, grant_type (the value for which should be client_credentials) and scope (value should be 'https://graph.microsoft.com/.default')
Finally, you can call the Graph API to manage your users, including deleting them:
DELETE https://graph.microsoft.com/v1.0/users/{upn}
Don't forget to include the access token in the header. I noticed that in Postman, the graph api had a bug and returned an error if I include the word 'Bearer' at the start of the Authorization header. Try without it and it works. I haven't tried it in my NodeJS API yet, so, can't comment on it so far.
#allen-wu also suggested using the ROPC flow, which I have not tried yet, so, cannot compare the two approaches.
I hope this helps!
There is a line of code: graphClient.getUsers("").buildRequest().delete();
It seems that you didn't put the user object id in it.
However, we can ignore this problem because Microsoft Graph doesn't allow a user to delete itself.
Here is the error when I try to do it.
{
"error": {
"code": "Request_BadRequest",
"message": "The principal performing this request cannot delete itself.",
"innerError": {
"request-id": "8f44118f-0e49-431f-a0a0-80bdd954a7f0",
"date": "2020-06-04T06:41:14"
}
}
}
I'm working on Android app which uses Foursquare API. For fetching results I use /venues/explore endpoint. I want to make fancy detail screen with set of photos as Foursquare does(example) So, according to documentation, when I make request for nearby places, I use parameter venuePhotos=1, but always get one photo from the count result of featured photos (example). Is this a bug of API or I got it wrong :) ?
I'm using hurl.it for monitoring response from certain requests.
here is my API request(I removed my client id and secret):
GET https://api.foursquare.com/v2/venues/explore?venuePhotos=1&limit=10&radius=500&v=20160215&ll=55.693053,12.584903&client_secret=//&client_id=//
client_id:
client_secret:
limit: 10
ll: 55.693053,12.584903
radius: 500
v: 20160215
venuePhotos: 1
And I get always one photo in photo count for regular and featured photos in every venue. Response example:
{"reasons": {},"venue": {"id": "4c41ae26af052d7fbba97d79","name": "Femmeren - jazz-værtshus","contact": {},"location": {},"categories": [],"verified": false,"stats": {},"price": {},"rating": 8.2,"ratingColor": "73CF42","ratingSignals": 11,"allowMenuUrlEdit": true,"photos": {"count": 1,"groups": []},"hereNow": {},"featuredPhotos": {"count": 1,"items": []}},"tips": [],"referralId": "e-0-4c41ae26af052d7fbba97d79-3"},
Thanks for clarifying your question. The venuePhotos=1 parameter will include a single photo in the response from the venues/explore endpoint. This is extremely useful if you need to display a single photo in a list of venues. To get more photos for a venue, you need to hit the venues/photos endpoint and pass the venueId you want the photos from -> https://api.foursquare.com/v2/venues/VENUE_ID/photos
I've been wracking my brain these past two days to try and understand how to use the authentication built into ASP.NET's WebAPI 2 using Google as an external authentication, and not being familiar with OAuth 2, I'm quite lost. I have followed this tutorial to set up the sign-in button on my Android client and send the "idToken" to the Web API. I've also followed this (now out of date) tutorial on setting up Google as an external login.
The problem happens when I try to send it I get {"error":"unsupported_grant_type"} as a response. Some other tutorials lead me to believe that the POST to mysite.com/token does not contain the correct data. This means I'm either building the request incorrectlyon the client, I'm somehow handling it incorrectly on the backend, I'm sending it to the wrong url, or I'm doing something entirely else wrong.
I found this SO answer which says to get a URL from /api/Accounts/ExternalLogins, but the sign-in button already gives me the access token that would supply to me (if I understand that correctly).
If someone could help me out here on what the exact process should be from start to finish, that would be amazing.
UPDATE: Okay, so here are some things that I've learned since I asked this question.
website.com/token URI is the redirect for the built in OAuth server in the WebAPI2 template. This is not useful for this particular problem.
The id_token is an encoded JWT token.
The website.com/signin-google URI is the redirect for normal Google login, but does not accept these tokens.
I may have to write my own AuthenticationFilter that uses the Google Client library to authorize through the Google API.
UPDATE 2: I'm still working on getting this AuthenticationFilter Implementation. Things seem to be going well at this point, but I'm getting stuck on some things. I've been using this example to get the token verification code, and this tutorial to get the AuthenticationFilter code. The result is a mix of both of them. I'll post it here as an answer once it's complete.
Here are my current problems:
Producing an IPrincipal as output. The verification example makes a ClaimPrincipal, but the AuthenticationFilter example code uses a UserManager to match the username to an existing user and returns that principal. The ClaimsPrincipal as created in the verification example directly does not auto-associate with the existing user, so I need to attempt to match some element of the claims to an existing user. So how do I do that?
I still have an incomplete idea of what a proper flow for this is. I'm currently using the Authentication header to pass my id_token string using a custom scheme: "goog_id_token". The client must send their id_token for every method called on the API with this custom AuthenticationFilter. I have no idea how this would usually be done in a professional environment. It seems like a common enough use case that there would be tons of information about it, but I haven't seen it. I have seen the normal OAuth2 flow, and since I'm only using an ID Token, and not an Access Token I'm a bit lost on what an ID Token is supposed to be used for, where it falls in a flow, and where it's supposed to live in an HTTP packet. And because I didn't know these things, I've kind of been making it up as I go along.
Wow, I did it. I figured it out. I... I can't believe it.
As metioned in my question Update 2, this code is assembled from Google's official API C# example and Microsoft's Custom AuthenticationFilter tutorial and code example. I'm going to paste the AuthorizeAsync() here and go over what each block of code does. If you think you see an issue, please feel free to mention it.
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
bool token_valid = false;
HttpRequestMessage request = context.Request;
// 1. Look for credentials in the request
//Trace.TraceInformation(request.ToString());
string idToken = request.Headers.Authorization.Parameter.ToString();
The client adds the Authorization header field with the scheme followed by a single space, followed by the id token. It looks something like Authorization: id-token-goog IaMS0m3.Tok3nteXt.... Putting the ID token in the body as given in the google documentation made no sense in this filter so I decided to put it in the header. For some reason it was difficult to pull custom headers from the HTTP packets so I just decided to use the Authorization header with a custom scheme followed by the ID token.
// 2. If there are no credentials, do nothing.
if (idToken == null)
{
Trace.TraceInformation("No credentials.");
return;
}
// 3. If there are credentials, but the filter does not recognize
// the authentication scheme, do nothing.
if (request.Headers.Authorization.Scheme != "id-token-goog")
// Replace this with a more succinct Scheme title.
{
Trace.TraceInformation("Bad scheme.");
return;
}
This whole point of a filter is to ignore requests that the filter doesn't govern (unfamiliar auth schemes, etc), and make judgement on requests that it's supposed to govern. Allow valid authentication to pass to the downstream AuthorizeFilter or directly to the Controller.
I made up the scheme "id-token-goog" because I had no idea if there was an existing scheme for this use case. If there is, somebody please let me know and I'll fix it. I guess it doesn't particularly matter at the moment as long as my clients all know the scheme.
// 4. If there are credentials that the filter understands, try to validate them.
if (idToken != null)
{
JwtSecurityToken token = new JwtSecurityToken(idToken);
JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler();
// Configure validation
Byte[][] certBytes = getCertBytes();
Dictionary<String, X509Certificate2> certificates =
new Dictionary<String, X509Certificate2>();
for (int i = 0; i < certBytes.Length; i++)
{
X509Certificate2 certificate =
new X509Certificate2(certBytes[i]);
certificates.Add(certificate.Thumbprint, certificate);
}
{
// Set up token validation
TokenValidationParameters tvp = new TokenValidationParameters()
{
ValidateActor = false, // check the profile ID
ValidateAudience =
(CLIENT_ID != ConfigurationManager
.AppSettings["GoogClientID"]), // check the client ID
ValidAudience = CLIENT_ID,
ValidateIssuer = true, // check token came from Google
ValidIssuer = "accounts.google.com",
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
CertificateValidator = X509CertificateValidator.None,
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
return identifier.Select(x =>
{
// TODO: Consider returning null here if you have case sensitive JWTs.
/*if (!certificates.ContainsKey(x.Id))
{
return new X509SecurityKey(certificates[x.Id]);
}*/
if (certificates.ContainsKey(x.Id.ToUpper()))
{
return new X509SecurityKey(certificates[x.Id.ToUpper()]);
}
return null;
}).First(x => x != null);
},
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromHours(13)
};
This is all unchanged from the Google example. I have almost no idea what it does. This basically does some magic in creating a JWTSecurityToken, a parsed, decoded version of the token string, and sets up the validation parameters. I'm not sure why the bottom portion of this section is in it's own statement block, but it has something to do with the CLIENT_ID and that comparison. I'm not sure when or why the value of CLIENT_ID would ever change, but apparently it's necessary...
try
{
// Validate using the provider
SecurityToken validatedToken;
ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken);
if (cp != null)
{
cancellationToken.ThrowIfCancellationRequested();
ApplicationUserManager um =
context
.Request
.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
Get the user manager from the OWIN context. I had to dig around in context intellisense until I found GetOwinCOntext(), and then found that I had to add using Microsoft.Aspnet.Identity.Owin; in order to add the partial class that included the method GetUserManager<>().
ApplicationUser au =
await um
.FindAsync(
new UserLoginInfo(
"Google",
token.Subject)
);
This was the very last thing I had to fix. Again, I had to dig through um Intellisense to find all of the Find functions and their overrides. I had noticed from the Identity Framework-created tables in my database that there is one called UserLogin, whose rows contain a provider, a provider key, and a user FK. The FindAsync() takes a UserLoginInfo object, which contains only a provider string and a provider key. I had a hunch that these two things were now related. I had also recalled that there was a field in the token format that included a key-looking field that was a long number that started with a 1.
validatedToken seems to be basically empty, not null, but an empty SecurityToken. This is why I use token instead of validatedToken. I'm thinking there must be something wrong with this, but since the cp is not null, which is a valid check for a failed validation, it makes enough sense that the original token is valid.
// If there is no user with those credentials, return
if (au == null)
{
return;
}
ClaimsIdentity identity =
await um
.ClaimsIdentityFactory
.CreateAsync(um, au, "Google");
context.Principal = new ClaimsPrincipal(identity);
token_valid = true;
Here I have to create a new ClaimsPrincipal since the one created above in validation is empty (apparently that's correct). Took a guess on what the third parameter of CreateAsync() should be. It seems to work that way.
}
}
catch (Exception e)
{
// Multiple certificates are tested.
if (token_valid != true)
{
Trace.TraceInformation("Invalid ID Token.");
context.ErrorResult =
new AuthenticationFailureResult(
"Invalid ID Token.", request);
}
if (e.Message.IndexOf("The token is expired") > 0)
{
// TODO: Check current time in the exception for clock skew.
Trace.TraceInformation("The token is expired.");
context.ErrorResult =
new AuthenticationFailureResult(
"Token is expired.", request);
}
Trace.TraceError("Error occurred: " + e.ToString());
}
}
}
}
The rest is just exception catching.
Thanks for checking this out. Hopefully you can look at my sources and see which components came from which codebase.
I am having trouble while trying to post a message on the current end-user Facebook wall with my Android Application.
So far :
The user is logged in with all the required permissions, especially
the publish_actions permission ;
I am using the last version of Facebook SDK 3.x & Graph API v2 as of May 2014
Post to wall works now as of EDIT 2, but there are issues with
privacy settings : post cannot be seen by anyone not even the
recipient who is tagged in the post ! See EDIT 2 for more details
Here is the documentation for posting a message on the user wall :
https://developers.facebook.com/docs/graph-api/reference/v2.0/user/feed (go to Publishing)
Regarding the message that needs to be posted on the user' wall, I need to tag some friends that were previously selected by the user.
But to use the tag field I need to use the place field.
The documentation says :
Name: tags
Comma-separated list of user IDs of people tagged in this post. You cannot specify this field without also specifying a place.
Type: csv[string]
Name: place
Page ID of a location associated with this post.
Type: string
What is location page ID ? How do I get it ?
Oddly enough, in my specefic case, I am offering the possibilty to the user to share is upcoming travel on Facebook.
More oddly enough, this travel is defined by dates and a destination (name of destination + latitude & longitude). The user has the possibility to also share this travel to his friends that are located in his travel's destination hence the need to tag his friends to the wall post.
[EDIT 1]
After looking into #Tobi solutions, I managed to post a message on the end-user wall (in my case me, since I am the developper).
So far : the message is correctly displayed with a friend tagged (a dummy account I use for tests purpouses) and there is also the place : Paris, France.
Using the following search query (thanks #Tobi) search?q=Paris,France&type=place I can get the ID of Paris, France.
Since I did not code the part to retrieve the Place ID it put it manually in the code for the sake of testing the feature first.
[END OF EDIT 1]
[EDIT 2]
I noticed I forgot to add the part with the privacy values to my args Bundle. But it did not change a damn thing : my post is only visible to me alone.
Going to my Facebook wall, here is what I get :
Hovering the public parameters of the posts (a padlock icon) says "All tagged persons".
Clicking on the icon shows the list of settings with only "Only me" selected.
Why "only me" even though I specifically said me + my dummy account in the request parameters ?
Even weirder : changing the property of the publication parameters to "customized" with my dummy account does not change a damn thing as well. Just why ? Facebook is really getting on my nerve...
For the rest, the message content is correctly displayed and the related place is the good one. At least a got that correctly. But still, I just don't get the rest ...
[END OF EDIT 2]
So here is the updated code :
if (this.session != null && this.session.isOpened()) {
final Bundle args = new Bundle();
if (this.selectedContacts.isEmpty() == false) {
args.putString("message", message);
args.putString("place", "170558129707208"); //manually added Paris, France ID
String tags = "";
//here I made some modifications regarding the tags so that is built correctly
for (int it = 0; it < this.selectedContacts.size(); it++) {
final String name = this.selectedContacts.get(it).toString();
final String friendID = String.valueOf(getIdFromName(name)); // returns an int
if (it == this.selectedContacts.size())
tags += friendID;
else
tags += friendID + ",";
}
// Forgot to add privacy values to bundle !
final JSONObject privacyValues = new JSONObject(); //not android JSON, but JSON-Simple lib
privacyValues.put("value", "CUSTOM");
String allow = this.currentUser.getId() + "," + tags;
privacyValues.put("allow", allow); // to limit the visibility of this post
args.putString("privacy", privacyValues.toJSONString()); //forgot that one...
final Request shareTravelRequest = new Request(this.session, this.currentUser.getID() + "/feed",
args, HttpMethod.POST, new Request.Callback() {
public final void onCompleted(final Response response) {
//TODO
}
}
);
final Response response = shareTravelRequest.executeAndWait();
if (response.getError() == null) // in this case, no error occurred
return true;
else {
this.lastErrorMessage = response.getError().getErrorMessage();
Log.e("Something went wrong while posting Facebook message", this.lastErrorMessage);
Log.e("Error type is", response.getError().getErrorType());
Log.e("Error code is", String.valueOf(response.getError().getErrorCode()));
return false;
}
}
}
Unfortunately I must have mistaken somewhere because although the post does appear on my Facebook wall with my dummy account tagged and with the correct link to the place, my dummy account cannot see the post.
I tried creating manually the same post on my Facebook wall by using the following settings :
First I supplied a message
Second I supplied a place
Third I tagged my dummy account
Four I choose who can see this post : me and my dummy account
And finally, I post the message.
In this case, my dummy account sees the post both on its wall and on my wall.
So I would like to know what parameters I must get wrong because there is not much parameters and I just don't see why.
Thanks !
You can either use the Search and use the type place (https://developers.facebook.com/docs/graph-api/using-graph-api/v2.0#search), or, if you have positioning date, use an FQL query on the place table (https://developers.facebook.com/docs/reference/fql/place/) as described here: Facebook places: order results by distance
Use two test accounts made via Facebook Developer App settings and not a dummy account that breaks TOS. Since you request publish_actions you probably didn't submit the app for review. Unless your dummy account is a developer/tester in roles as well (which is breaking the TOS again) then your dummy account will not be able to see the post.
Only developers and testers of an unpublished application can see posts made by that application.
I'm trying to discover if a user likes a specific facebook page by using Graph API inside an android application using the FB SDK v3.
The following call works and reports all of the likes of the user along with every other detail to do with each resulting item.
Request.executeGraphPathRequestAsync(session, "me/likes", new Request.Callback()
Since I'm not interested in any peripheral data, I just want to get the id back so I can lookup the page I'm interested in, but when I add the parameters as follows...
Request.executeGraphPathRequestAsync(session, "me/likes?fields=id", new Request.Callback()
... no object is returned from the call. If I post the same queries in the Graph API explorer they give back the expected results.
In addition to this question, is it at all possible to limit the result of the query to just the page ID to begin with, since I already know that piece of information? I have no use for the rest of the data being returned and would prefer not to chew up user bandwidth unnecessarily. I've searched high and low and it doesn't seem possible. Even the FQL explorer doesn't work when trying to limit by both user and page iD as the example suggests. I get an empty result set when adding 'AND page_id=xxx' to the query.
As far as i know, you cant run queries with field in 'executeGraphPathRequestAsync'.
instead use 'Request' and run in with 'RequestAsyncTask'.
Short example:
String grapPath = "me/home";
Bundle params = new Bundle();
params.putString("fields", "type,id,object_id,created_time,from,picture,likes");
params.putString("limit", "500");
Request request = new Request(session, grapPath, params, HttpMethod.GET, new Callback()
{
#Override
public void onCompleted(Response response)
{
if (response != null)
{
Log.d(TAG_NAME, "response: " + response.toString());
}
}
});
RequestAsyncTask task = new RequestAsyncTask(request);
task.execute();
Hope i managed to help you :)
The Facebook SDK will add the "?access_token" to the end of the graph path string. So the graph command ends up looking like:
me/likes?fields=id?access_token
Break out the fields into a params Bundle.
Found this issue resolved in a Facebook bug post:
http://developers.facebook.com/bugs/314504765319932?browse=search_5116b87d245900257505334