I created a model called User that have a property "name".
I'm making a request to facebook API (using the latest iOS SDK), the idea is set my user.name property when the facebook return the data.
The facebook return the data into the startWithCompletionHandler block, but I can't set this data to my user object, I only can access the data on startWithCompletionHandler. When I try access the data out of the block my model returns NULL.
How can I fill my model/object when facebook request returns?
My current method implementation:
+ (void)requestUserData:(User *)userModel {
if(FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *user, NSError *error) {
if(user.name) [self setUserData:user userModel:userModel];
}];
}
}
+ (void)setUserData:(NSDictionary<FBGraphUser> *)user userModel:(User *)userModel {
userModel.name = user.name;
}
And the call:
__block User *user = [[User alloc] init];
[GFFacebookHelpers requestUserData:user];
NSLog(#"user: %#", user.name); //this part prints 2013-06-06 18:03:43.731 FacebookClassTest[74172:c07] user: (null)
Thanks.
The Facebook's SDK is fetching asynchronously, so the completion handler you've written is executed after your helper method returns.
So when your method returns the User object of yours it is, logically just an empty user (since you alloc/init'ed it already).
When the completion block is invoked, that user object is updated but by this time, you're not processing it anymore in your app (view controller).
So here is my suggestion: Either use the Facebook SDK more directly from your view controllers, or if you want to keep all this logic in your helper class, then change it so your method doesn't return data, but it requires a block which will be invoked when the data request has finished, something more like this:
+ (void)requestUserDataWithCompletionHandler:(void (^)(User *user, NSError *error))handler{
if(FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *user, NSError *error) {
if(!error){
User *userData = [[User alloc] init];
userData.name = user.name;
dispatch_async(dispatch_get_main_queue(), ^{
handler(userData, nil);
});
}
else{
dispatch_async(dispatch_get_main_queue(), ^{
handler(nil, error);
});
}
}];
}
}
Note I've wrapped the invocation of the handler block in a dispatch_async() to the main queue to make sure you're good to go to update any UI.
Your view controller's method will now look like this:
- (void)updateStatusLabels {
if([GFFacebookHelpers isLogged]){
[GFFacebookHelpers requestUserDataWithCompletionHandler:^(User *user, NSError *error) {
if(!error){
self.fbStatus.text = user.name;
[_loginButton setTitle:#"Logout" forState:UIControlStateNormal];
}
}];
}else{
self.fbStatus.text = #"You need to login";
[_loginButton setTitle:#"Login" forState:UIControlStateNormal];
}
}
Related
As an incident of a user taking some action in my app, I want to post an image to Facebook on their behalf. Let's assume the user has already granted me publish_actions permission in class LoginVC (one time permission is used for ad infinitum posting in the future). Then at some in the future, in ActionVC, I want to publish a photo to Facebook. How do I do that? Here is the method I need to implement:
- (void)publishPhoto:(UIImage *)image
{
//what goes in here?
}
So far I have been looking at the samples from Facebook. The closest I come is the following, but it seems to be using a Dialog. But I don't want the user to "know" that the photo is being posted. They already granted the permission and I want the posting to happen without their knowledge as it were. So some other action has triggered the call to publish...
For reference, the code from the Facebook sample looks like this
- (void)publishPhoto:(UIImage *)image
{
BOOL canPresent = [FBDialogs canPresentShareDialogWithPhotos];
NSLog(#"canPresent: %d", canPresent);
FBPhotoParams *params = [[FBPhotoParams alloc] init];
params.photos = #[image];
BOOL isSuccessful = NO;
if (canPresent) {
FBAppCall *appCall = [FBDialogs presentShareDialogWithPhotoParams:params
clientState:nil
handler:^(FBAppCall *call, NSDictionary *results, NSError *error) {
if (error) {
NSLog(#"Error: %#", error.description);
} else {
NSLog(#"Success!");
}
}];
isSuccessful = (appCall != nil);
}
if (!isSuccessful) {
[self performPublishAction:^{
FBRequestConnection *connection = [[FBRequestConnection alloc] init];
connection.errorBehavior = FBRequestConnectionErrorBehaviorReconnectSession
| FBRequestConnectionErrorBehaviorAlertUser
| FBRequestConnectionErrorBehaviorRetry;
[connection addRequest:[FBRequest requestForUploadPhoto:image]
completionHandler:^(FBRequestConnection *innerConnection, id result, NSError *error) {
[self showAlert:#"Photo Post" result:result error:error];
if (FBSession.activeSession.isOpen) {
self.buttonPostPhoto.enabled = YES;
}
}];
[connection start];
self.buttonPostPhoto.enabled = NO;
}];
}
}
Sorry if this question seems too easy, but I am a newbie to Facebook SDK integration
Generally you definitely want the user to be aware that something is being posted on their behalf, but to answer your question, if they've already granted you publish permissions, then you can use the code in the second "if" statement that you posted above, where it calls FBRequest requestForUploadPhoto:
I have some code which queries facebook for information
if (FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:
^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error,) {
if (!error) {
//populate the **mUser** with data
} else {
NSLog(#"Facebook error encountered: %#", error);
}
}];
} else {
NSLog(#"Facebook session is closed");
}
My question is, what is the best way to tell the caller of the function that facebook is finished? I can't simply return from inside the block (incompatible block pointer types).
The calling code looks like this:
myfacey *fb = [[myfacey alloc] init];
[fb getUserFromFacebook: mUser];
//Need to access a populated mUser object here
//calls to mUser result in nil values because facebook hasn't finished
If facebook accessed things synchronously I would have no problem, because mUser would have valid data in it.
Since I have to make asynch calls, what is the best way to signal the calling class that facebook is done populating the variable?
The best way to continue with your program once the asynchronous method has been finished, is putting this code into the completion handler.
Don't try to "return", just "continue" the program in the completion handler.
You should just care about the "execution context" (say thread or dispatch queue) on which the completion handler will be invoked from the async method: if this is not explicitly documented, the completion handler may be called on any thread. So, you might want to dispatch explicitly to the main thread - if necessary:
if (FBSession.activeSession.isOpen) {
[[FBRequest requestForMe] startWithCompletionHandler:
^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error,) {
if (!error) {
// Continue here with your program.
...
// If you need ensure your code executes on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
// continue with your program on the main thread, for example:
// populate the **mUser** with data
[self.tableView reloadData];
...
})
} else {
NSLog(#"Facebook error encountered: %#", error);
}
}];
} else {
NSLog(#"Facebook session is closed");
}
I'm experimenting trying to call FBRequest:requestForMe:startWithCompletionHandler and couldn't get it to work in my own code so have tried adding it to the example project SessionLoginSample but still get the same result which is that the result is nil.
I've added to the updateView method within the example project, but it makes no difference where its placed, id is always nil:
- (void)updateView {
// get the app delegate, so that we can reference the session property
SLAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
if (appDelegate.session.isOpen) {
// valid account UI is shown whenever the session is open
[self.buttonLoginLogout setTitle:#"Log out" forState:UIControlStateNormal];
[self.textNoteOrLink setText:[NSString stringWithFormat:#"https://graph.facebook.com/me/friends?access_token=%#",
appDelegate.session.accessTokenData.accessToken]];
FBRequest *me = [FBRequest requestForMe];
[me startWithCompletionHandler:^(FBRequestConnection *connection,
id result,
NSError *error) {
NSDictionary<FBGraphUser> *my = (NSDictionary<FBGraphUser> *) result;
NSLog(#"My dictionary: %#", my.first_name);
}];
} else {
// login-needed account UI is shown whenever the session is closed
[self.buttonLoginLogout setTitle:#"Log in" forState:UIControlStateNormal];
[self.textNoteOrLink setText:#"Login to create a link to fetch account data"];
}
}
The NSError contains domain: com.facebook.sdk code: 5.
After some googling and experimentation I got requestForMe to work by adding a call to [FBSession setActiveSession:appDelegate.session] just prior to it, however I can not transfer this working code into my own project where I get the same error code of 5, however I cannot see any difference with my project's code to that of the example project.
After lots of googling I managed to fix this by adding the following line just before the call to requestForMe
[FBSession setActiveSession:appDelegate.session];
I've integrated with Facebook so that I can, among other things, post statuses to my feed. I based some of my code off of the publish to feed developer tutorial. When running the following Graph API request from my iOS application the completion block of the request is never called and no error appears in the XCode debug log.
[FBRequestConnection
startWithGraphPath:#"me/feed"
parameters:params
HTTPMethod:#"POST"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (error) {
DLog(#"error: domain = %#, code = %d", error.domain, error.code);
} else {
DLog(#"Posted action, id: %#", result[#"id"]);
}
}];
I have two convenience functions that perform checks against the current activeSession before executing this request. They look like this:
+ (BOOL)facebookSessionIsOpen {
return (FBSession.activeSession.isOpen);
}
+ (BOOL)facebookSessionHasPublishPermissions {
if ([FBSession.activeSession.permissions indexOfObject:#"publish_actions"] == NSNotFound ||
[FBSession.activeSession.permissions indexOfObject:#"publish_stream"] == NSNotFound ||
[FBSession.activeSession.permissions indexOfObject:#"manage_friendlists"] == NSNotFound) {
return NO;
} else {
return YES;
}
}
Both of these functions return YES indicating an active session with the necessary publishing permission. What's more confusing is that I can pull the user's profile without issue after performing the same checks successfully (granted publishing permissions are not required to pull the profile) using the following code:
[FBRequestConnection
startWithGraphPath:#"me"
parameters:[NSDictionary dictionaryWithObject:#"picture,id,birthday,email,location,hometown" forKey:#"fields"]
HTTPMethod:#"GET"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
NSDictionary* resultDict = (NSDictionary*)result;
NSString* emailAddress = resultDict[#"email"];
NSString* location = resultDict[#"location"][#"name"];
NSString* birthday = resultDict[#"birthday"];
NSString* homeTown = resultDict[#"hometown"][#"name"];
...
}];
Any suggestions on how to debug this issue?
Turns out the issue was a threading one. The Facebook iOS SDK doesn't seem to like to execute a FBRequest on a different thread from the one that you called openActiveSessionWithReadPermissions on and promptly deadlocks. It turns out I was running the postStatus request in a separate thread like so:
dispatch_queue_t some_queue = dispatch_queue_create("some.queue.name", NULL);
dispatch_async(some_queue, ^{
[FacebookHelper postStatusToFacebookUserWall:newStatus withImage:imageData];
}];
Make sure your openActiveSessionWithReadPermissions and any FBRequest permutations all happen on the same thread, or you'll likely run into these silent failures.
I'm using the new facebook ios sdk. I request for friends data using the new function showed below. However, since it is a function with a block as a parameter I lost these data outside the function. How can I preserve the data (i.e. store in a global variable) so that I can use it in another function?
Thanks in advance.
code:
-(void)requestFriends {
[FBRequestConnection startForMyFriendsWithCompletionHandler:^(FBRequestConnection* connection, id data, NSError *error) {
if(error) {
[self printError:#"Error requesting /me/friends" error:error];
return;
}
NSArray* friends = (NSArray*)[data data];
}];
Just store it on a property, and refresh the UI after that.
// in .h or class extension
#property(nonatomic, strong) NSArray *friends;
-(void)requestFriends {
[FBRequestConnection startForMyFriendsWithCompletionHandler:^(FBRequestConnection* connection, id data, NSError *error) {
if(error) {
[self printError:#"Error requesting /me/friends" error:error];
return;
}
self.friends = (NSArray*)[data data];
}];