The code below is trying to lazy login to Facebook right before posting a photo, but has an asynchronous problem. In the logs, the after isSessionValid block
will appear before the fbDidLogin
and then a facebookErrDomain error 10000
will happen ("OAuthException", "active access token must be used", etc).
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
if (![appDelegate.facebook isSessionValid]) {
[appDelegate.facebook authorize:[NSArray arrayWithObjects:@"publish_stream", @"user_photos", nil]];
}
NSLog(@"after isSessionValid block");
NSData *imageData = UIImageJPEGRepresentation(image, 1);
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
FACEBOOK_APP_ID, @"app_id",
imageData, @"source",
message, @"message",
nil];
[appDelegate.facebook requestWithGraphPath:@"me/photos" andParams:params andHttpMethod:@"POST" andDelegate:self];
Here is the fbDidLogin
in MyAppDelegate
- (void)fbDidLogin {
NSLog(@"fbDidLogin");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[facebook accessToken] forKey:@"FBAccessTokenKey"];
[defaults setObject:[facebook expirationDate] forKey:@"FBExpirationDateKey"];
[defaults synchronize];
}
I realize that the facebook requestWithGraphPath
is running before the fbDidLogin
on the FBSessionDelegate
but not sure the best way to take the code below the after isSessionValid block
log statement and have it run inside fbDidLogin
?
Question
I would love to have a completionHandler
style API like below. Is there an easy way do that? Alternatively, is there good way to add a callback or block to MyAppDelegate
that would be called once from fbDidLogin
and then removed?
[appDelegate.facebook authorize:array completionHandler:^(BOOL success) {
// other setup stuff from first example
[appDelegate.facebook requestWithGraphPath:@"me/photos" andParams:params andHttpMethod:@"POST" andDelegate:self];
}];
Update
An answer to How to react to asynchronous events (login)? might be what I am looking for.
Here is what I put in MyAppDelegate
, which saves off a completionHandler, and either calls with YES
or NO
depending on login, then sets completionHandler to nil
. I'm sure I should put a @try
/@catch
in there somewhere to ensure completionHandler gets set to nil.
@interface MyAppDelegate : UIResponder <UIApplicationDelegate, FBSessionDelegate>
{
void (^_completionHandler)(BOOL success);
}
- (void)facebookAuthorizeWithCompletionHandler:(void (^)(BOOL success))completionHandler {
if (![facebook isSessionValid]) {
_completionHandler = completionHandler;
[facebook authorize:[NSArray arrayWithObjects:@"publish_stream", @"user_photos", nil]];
} else {
completionHandler(YES);
}
}
- (void)fbDidLogin {
NSLog(@"fbDidLogin");
// removed code that saves accessToken/expirationDate to NSUserDefaults
if (_completionHandler) {
_completionHandler(YES);
_completionHandler = nil;
}
}
- (void)fbDidNotLogin:(BOOL)cancelled {
NSLog(@"fbDidNotLogin");
if (_completionHandler) {
_completionHandler(NO);
_completionHandler = nil;
}
}
I called into it using code like this, which seems to work (but I want to reread about Blocks and Variables to make sure I understand the memory management issues).
[appDelegate facebookAuthorizeWithCompletionHandler:^(BOOL success) {
NSLog(@"in completionHandler, success=%d", success);
if (success) {
// other setup stuff from first example
[appDelegate.facebook requestWithGraphPath:@"me/photos" andParams:params andHttpMethod:@"POST" andDelegate:self];
}
}];