I've got an app that is nearing completion, and we've decided to allow users to login using Facebook.
At some point in the past, this feature was working, and now it simply hangs.
I've included the latest Parse SDK (1.6.1) and the latest Facebook SDK (3.21.1).
I think I've set up the app correctly. Here are the calls that I am making:
// AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[Parse setApplicationId:#"appId" clientKey:#"clientKey"];
[PFFacebookUtils initializeFacebook];
return YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
[FBAppCall handleDidBecomeActiveWithSession:[PFFacebookUtils session]];
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
[[PFFacebookUtils session] close];
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication withSession:[PFFacebookUtils session]];
}
// Login View Controller
- (void)loginWithFacebok {
[PFFacebookUtils logInWithPermissions:#[#"public_profile", #"email"] block:^(PFUser *user, NSError *error) {
if (error != nil) {
// Error handling here.
} else {
if (user == nil) {
// Something odd happened; error handling here.
} else {
// A further Facebook method...
[self authorizeFacebookPublish];
}
}
}];
}
Basically, although it was working at a previous time, it's non-functional now in the sense that block is never called. I do get the "app jump" to the Facebook app/web page (can't remember which now, and it "jumps" there and back too fast for me to tell, since I've approved this in the past). What's interesting is that if I force-quit the app (the app is basically in a "hung" state) and re-launch the app, the app loads and the user is logged in.
This is a fairly important point, but I can't find anyone else having this particular issue.
I saw this question, but we're not using Twitter.
This question isn't related to Parse.
EDIT:
I was working through this more and kept being slightly confused by breakpoint catching. For instance, in my -loginWithFacebook method, I placed a breakpoint on the first line (the loginWithPermissions call) and the first line of the returned block (if (error != nil) {..., and the first breakpoint would be hit twice each time. I finally thought to try something new, so I added an NSLog line above the first line and moved the first breakpoint there. Subsequent runs had the first breakpoint (now on NSLog) hit once and then the first line of the block hit second. Therefore, I was able to determine that my problem is not, in fact, in my loginWithFacebook method. It's in my authorizeFacebookPublish method. Here's that code:
- (void)authorizeFacebookPublish {
NSLog(#"authorizing for Facebook publish..."); // First breakpoint here...
[PFFacebookUtils reauthorizeUser:[PFUser currentUser]
withPublishPermissions:#[#"publish_actions"]
audience:FBSessionDefaultAudienceFriends
block:^(BOOL succeeded, NSError *error) {
if (error != nil) { // Second breakpoint here...
// Error handling...
} else {
if (succeeded == YES) {
// Our app now has publishing permissions for the user
[PFUser currentUser][#"canPublishToFacebook"] = #(YES);
} else {
// Our app was refused publishing permissions
[PFUser currentUser][#"canPublishToFacebook"] = #(NO);
[[PFUser currentUser] saveInBackground];
}
// Log in was successful, so do other stuff....
}
}];
}
I'm now seeing the issue that's also listed here, which is that some breakpoint (apparently named "breakpoint 2.1" is being hit each time). This guy, however, isn't reporting the same problem I'm seeing, which is that the return block is never firing. Therefore, I am concluding that there is some problem in the authorization (or maybe re-authorization) block.
As I mentioned above, this code was working at some point in the past. Is it possible that "re-authorizing" causes an error? Is there a way for me to check whether the user has already authorized my app in the past and skip the authorization process (since it's already been done)? If that IS the problem, what's the correct procedure for logging a user back in after a logout (using Facebook)?
First of all, why are you calling [[PFFacebookUtils session] close] in your applicationWillTerminate: callback ?
The Facebook session uses a token stored on disk, which means the session is kept open across app launches and has no reason to be closed when the app is terminated.
If you want to log the user out, you should call [PFUser logOut] and then [[PFFacebookUtils session] close] or better [[PFFacebookUtils session] closeAndClearTokenInformation] (but I'm not sure this is even necessary).
If you don't want to log the user out (which I think is better), just don't call anything.
Secondly, I too have had a lot of problems in the past with [PFFacebookUtils reauthorizeUser: ...]. One workaround that I found was to use the Facebook API directly :
[[PFFacebookUtils session] requestNewPublishPermissions:[NSArray arrayWithObject:#"publish_actions"] defaultAudience:FBSessionDefaultAudienceFriends completionHandler:^(FBSession *session, NSError *error) {
if (!error) {
if ([[PFFacebookUtils session].permissions indexOfObject:#"publish_actions"] == NSNotFound) {
// Permission not granted, tell the user we will not share to Facebook
NSLog(#"Permission not granted, we will not share to Facebook.");
} else {
// Permission granted.
}
} else {
// An error occurred. See: https://developers.facebook.com/docs/ios/errors
NSLog(#"Error : Requesting \"publish_actions\" permission failed with error : %#", error);
}
}];
It does exactly the same thing but seems to be working better.
Also, concerning your multiple breakpoint issues, if you put a breakpoint at the beginning of a block, it will usually stop twice : when the block is created and when the block is called. The block is only called once though, you can check that with an NSLog(...) call as you did.
Related
I'm utilizing the facebook log in from Azure SDK, I'm utilizing the following code in a button:
-(void)fblogin
{
[_azureService.client loginWithProvider:#"facebook" urlScheme:#"dribel" controller:self animated:YES completion:^(MSUser * _Nullable user, NSError * _Nullable error)
{
if (error)
{
NSLog(#"Login failed with error: %#, %#", error, [error userInfo]);
}
else
{
_azureService.client.currentUser = user;
NSLog(#"User logged in: %#", user.userId);
[self fblogin];
}
}];
}
A webview like this, opens and does all the facebook log in process...
And when it ends, it calls the following method in the app delegate and since it's true, it resumes the log in flow...
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
if ([[url.scheme lowercaseString] isEqualToString:#"dribel"])
{
// Resume login flow
return [self.azureService.client resumeWithURL:url];
}
else {
return NO;
}
}
but the webview is supposed to be dismissed and after being dismissed the code is supposed to land on the else statement of the first piece of code, but it's not doing anything. I have to press done and lands on the if true statement and it says I canceled it. Does anyone has an idea of what could be wrong?
We have set up the external url in the azure portal to redirect to the correct appname:// so that's why it we get true in the app delegate. But what else is missing?
I’m testing code based on the firechat-ios example. I’ve added the FirebaseSimpleLogin call loginToFacebookAppWithId and have it set up so that one view controller performs the login and then transitions to a different view controller that holds the chat logic:
self.firebase = [[Firebase alloc] initWithUrl:#"https://xxxxx.firebaseio.com/"];
[self observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"%#", snapshot.value);
// Add the chat message to the array.
[self.chat addObject:snapshot.value];
// Reload the table view so the new message will show up.
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}];
FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:self.firebase];
[authClient loginToFacebookAppWithId:kFacebookAppID permissions:#[#"email"]
audience:ACFacebookAudienceOnlyMe
withCompletionBlock:^(NSError *error, FAUser *user) {
if (error != nil) {
// There was an error logging in
NSLog(#"facebook error");
} else {
// We have a logged in facebook user
NSLog(#"facebook logged in");
[authClient checkAuthStatusWithBlock:^(NSError* error, FAUser* user) {
if (error != nil) {
// Oh no! There was an error performing the check
NSLog(#"auth error");
} else if (user == nil) {
// No user is logged in
NSLog(#"auth not logged in");
} else {
// There is a logged in user
NSLog(#"auth logged in");
// segue to the chat view controller
[self performSegueWithIdentifier:#"segueToViewController" sender:self];
}
}];
}
}];
Here are the firebase rules:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
The problem is, about 10% of the time, the UITableView of the chat messages is blank, and I don’t see any chat message entries in the log. I’ve tried playing around with the order of observeEventType, putting it before and after the loginToFacebookAppWithId call.
I’m wondering if there is a race condition where maybe the messages are arriving before I call observeEventType. I’ve checked the return value of observeEventType and I get a FirebaseHandle of 1 even when no messages arrive. I’ve also upgraded the firebase framework that comes with firechat ios to https://cdn.firebase.com/ObjC/Firebase.framework-LATEST.zip and it still fails.
I thought that maybe the connection dropped, but I’m able to post messages with childByAutoId after I’ve authenticated and see them appear on the firebase server. I just never receive any messages.
I wonder if it’s trying to send me the messages in the brief moment before I’m authenticated, and failing because I don’t have read permission. Is there a way to delay event observations until after I’m in?
I’ve tried everything I can think of but I can’t make it work reliably.
---------- UPDATE ----------
I seem to be able to log in every time if I type my credentials manually. I'm currently checking for a previous successful login with:
[FBSession openActiveSessionWithAllowLoginUI:false]
To determine if I successfully logged in on the last launch of the app. If it fails, I go to a view controller for FirebaseSimpleLogin. But if it works, I call FirebaseSimpleLogin in the current view controller and wait till it succeeds in the background.
I'm running in the simulator, so I tried deleting the preferences plist at:
~/Library/Application Support/iPhone Simulator/7.0.3/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Library/Preferences/com.xxxxxxxxxx.plist
and relaunching, which forces me to re-authenticate. Then I tried typing in my credentials and logging in 25 times without a problem.
So I think the problem is either somehow related to trying to login with Facebook before I use FirebaseSimpleLogin, or logging in with credentials from the previous launch (without bringing up the login dialog). I'm still trying to narrow down the culprit.
---------- UPDATE 2 ----------
I just wanted to add a note that after further testing, I found that the call to:
[FBSession openActiveSessionWithAllowLoginUI:false]
has no effect on FirebaseSimpleLogin. If I skip that call altogether and simply substitute true or false there, I can reproduce the issue. The problem turned out to be a race condition, see my answer below.
I finally figured out what was happening, it was due a wrong assumption on my part about UIViewController message callbacks and CFRunLoop.
The code sample in my question was distilled down from my real code to remove extraneous calls, but it turns out the part I removed was actually the culprit. I had written a function to log in and wait until success or failure on the spot (rather than receiving the response in a block later) by using a run loop:
-(bool)loginUsingFacebookReturningError:(NSError**)error andUser:(FAUser**)user
{
__block NSError *errorTemp;
__block FAUser *userTemp;
[self loginUsingFacebookWithCompletionBlock:^(NSError *error, FAUser *user) {
errorTemp = error;
userTemp = user;
CFRunLoopStop(CFRunLoopGetCurrent());
}];
CFRunLoopRun(); // needs a timeout or way for the user to cancel but I haven't implemented it yet
if(error) *error = errorTemp;
if(user) *user = userTemp;
return !errorTemp && userTemp;
}
-(void)loginUsingFacebookWithCompletionBlock:(void (^)(NSError* error, FAUser* user))block
{
FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:self.firebase];
[authClient loginToFacebookAppWithId:kFacebookAppID permissions:#[#"email"]
audience:ACFacebookAudienceOnlyMe
withCompletionBlock:^(NSError *error, FAUser *user) {
if (error != nil) {
// There was an error logging in
NSLog(#"facebook error");
block(error, nil);
} else {
// We have a logged in facebook user
NSLog(#"facebook logged in");
[authClient checkAuthStatusWithBlock:block];
}
}];
}
This was called with:
NSError *error;
FAUser *user;
bool success = [self loginUsingFacebookReturningError:&error andUser:&user];
The way loginUsingFacebookReturningError works is, it calls loginUsingFacebookWithCompletionBlock which fires off the loginToFacebookAppWithId and checkAuthStatusWithBlock messages like usual, but then I start a run loop. The run loop allows processing to happen in the background, even though the main thread pauses on CFRunLoopRun() until the completion block calls CFRunLoopStop().
What I hadn't realized is that run loops continue to process the application's messages in the background. So while I thought program flow had stopped in viewDidLoad, it had actually continued and called viewWillAppear, which is where I had placed my call to observeEventType (because I assumed that authentication would be complete by the time the program got there).
This created a race condition where the program attached the observeEventType callback during the time that Facebook and Firebase were authenticating. 90% of the time, authentication had completed before observeEventType was called, but 10% of the time there was lag or other network delays and observeEventType was called prematurely.
I fixed the problem by moving the FirebaseSimpleLogin code to its own view controller in the storyboard, and using the completion block to initiate the segue to the next view controller, which installed the observeEventType callback.
So to summarize: the solution is to call FirebaseSimpleLogin's authentication, and then AFTER it has finished and the completion block is done, call observeEventType. Otherwise Firebase's rules will deny your request to see data that's only visible to authenticated users (which is correct).
Here is the final code, untested but the method works:
// only global for illustration purposes, should really go in a singleton or AppDelegate, or be passed through the segue to the next view controller
Firebase *gFirebase;
// LoginViewController (root view controller in storyboard)
- (void)viewDidLoad
{
[super viewDidLoad];
gFirebase = [[Firebase alloc] initWithUrl:#"https://xxxxx.firebaseio.com/"];
FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:gFirebase];
[authClient loginToFacebookAppWithId:kFacebookAppID permissions:#[#"email"]
audience:ACFacebookAudienceOnlyMe
withCompletionBlock:^(NSError *error, FAUser *user) {
if (error != nil) {
// There was an error logging in
NSLog(#"facebook error");
} else {
// We have a logged in facebook user
NSLog(#"facebook logged in");
[authClient checkAuthStatusWithBlock:^(NSError* error, FAUser* user) {
if (error != nil) {
// Oh no! There was an error performing the check
NSLog(#"auth error");
} else if (user == nil) {
// No user is logged in
NSLog(#"auth not logged in");
} else {
// There is a logged in user
NSLog(#"auth logged in");
// segue to the chat view controller
[self performSegueWithIdentifier:#"segueToViewController" sender:self];
}
}];
}
}];
}
// ViewController (destination of segueToViewController)
- (void)viewDidLoad
{
[super viewDidLoad];
[gFirebase observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"%#", snapshot.value);
// Add the chat message to the array.
[self.chat addObject:snapshot.value];
// Reload the table view so the new message will show up.
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}];
}
in htttps://developer.facebook.com they have give login with API call Sample, they asked to type following code in my app delegate.m file
// Whenever a person opens the app, check for a cached session
if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
// If there's one, just open the session silently, without showing the user the login UI
[FBSession openActiveSessionWithReadPermissions:#[#"basic_info"]
allowLoginUI:NO
completionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
// Handler for session state changes
// This method will be called EACH time the session state changes,
// also for intermediate states and NOT just when the session open
[self sessionStateChanged:session state:state error:error];
}];
it is showing me the error like ---- No visible #interface for 'AppDelegate' declares the 'sessionStateChanged:state:error:'
thanks in advance...
According to above link, You have to add this method in your app delegate.. But you can customize this method according to your view by state(state == FBSessionStateOpen... etc)
// This method will handle ALL the session state changes in the app
- (void)sessionStateChanged:(FBSession *)session state:(FBSessionState) state error:(NSError *)error
{
// If the session was opened successfully
// customize your code...
}
I am working on an iOS app using the latest FB SDK for native log in. When I switch my app off in "allow these apps to use your account" in the settings, an error "com.facebook.sdk error 2" is expected to come.
I am wondering is there any elegant way to solve this error even if "allow these apps to use your account" is off for my app? I have searched for the solution but all the answers are saying that You need to switch that option on. But I think the better way is that if user switches that option off, we can still let him log in, falling back to the fast-app-switch way seamlessly, just like he doesn't log into Facebook on his device at all. How can I do this in the newest FB SDK? Thanks!
====================================Update=========================================
I solve it using a deprecated function openActiveSessionWithPermissions:allowLoginUI:completionHandler
first we need to check whether user switch this option off:
self.useAccountAllowed = true;
ACAccountStore *accountStore;
ACAccountType *accountTypeFB;
if ((accountStore = [[ACAccountStore alloc] init]) &&
(accountTypeFB = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook] ) ){
NSArray *fbAccounts = [accountStore accountsWithAccountType:accountTypeFB];
id account;
if (!fbAccounts)
{
//do not log into FB on the device
}
else if ([fbAccounts count] == 0) {
[FBSession.activeSession closeAndClearTokenInformation];
self.useAccountAllowed = false; //user switch this option off
}
then in openSession function, using that deprecated function if self.useAccountAllowed is false:
if (self.useAccountAllowed) {
[FBSession openActiveSessionWithReadPermissions:nil allowLoginUI:YES completionHandler:^(FBSession* session, FBSessionState status, NSError* error){
[self sessionStateChanged:session state:status error:error];}];
}
else {
NSArray* lPermission = FBSession.activeSession.permissions;
[FBSession openActiveSessionWithPermissions:lPermission allowLoginUI:YES completionHandler:^(FBSession* session, FBSessionState status, NSError* error){
[self sessionStateChanged:session state:status error:error];}];
not sure whether it is a correct way.
This is how I solved it. On the AppDelegate implementation file, in the applicationDidBecomeActive method, use the regular [FBSession.activeSession handleDidBecomeActive] method, as recommended by the FB SDK documentation. Plus, add a new method that checks the user permissions in Settings (that I called checkPermissionSettings in the example below):
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(#"applicationDidBecomeActive: in NHOCAppDelegate");
//
// The flow back to your app may be interrupted (for ex: if the user clicks the Home button
// while if authenticating via the Facebook for iOS app).
// If this happens, the Facebook SDK can take care of any cleanup that may include starting a fresh session.
//
[FBSession.activeSession handleDidBecomeActive];
[self checkPermissionSettings];
}
//
// Verify if the user pressed the Home Button, went to Settings and deauthorized the app via "Allow These Apps to Use Your Account..."
// If so, redirect him to the login screen (this happens automagically, see below).
//
- (void)checkPermissionSettings
{
NSLog(#"checkPermissionSettings: in NHOCAppDelegate");
//
// Now 'startForMeWithCompletionHandler' may return 'FBSessionStateClosed' (meaning that the user probably unauthorized the app in Settings).
//
// If that is the case:
//
// - Hide the 'logged' View Controller
// - Remove it (NHOCLoggedVC) from the Notification Center
// - Show the 'login' View Controller
// - And finally add it (NHOCLoginVC) to the Notification Center, closing the loop
//
// Check the console for further info.
//
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id<FBGraphUser> user, NSError *error) {
if (!error) {
//
// Everything went fine... The app is in good shape.
// Notice that 'user.location' requires user_location permission
//
NSLog(#"user.location: %#: ", [user.location objectForKey:#"name"]);
}
}];
}
To make it work as designed, I also use Notification Center. You can check the entire example here:
FB SDK + Storyboards with Publish to Feed
I hope it helps.
Hi I was reading and reading tutorials here on http://developers.facebook.com/ .
About Login in to Facebook and publish Feed from application, my problem is I cannot login to Facebook at all.
And I can't get it work. It sucks, annoying, and there is no clear tutorial/explanation about this.
Im trying to login from my app, when safari open and then the URL were like going nuts
blinking..
And I dont get an error, why?
Is there any CLEAR tutorial About Loging in?
it works GREAT on simulator, but not on the Device.
Im on Xcode 4.5 beta right now, but it also doesn't work on 4.4.
I need Help!
[CLOSED]
EDIT: I fixed it! I was so stupid... I was struggling in seven days just to login to FB,
then i changed ( Cookies Allowed on the device ), Everything worked PERFECT!
Damn that device just needed some cookies.. Lol
I just updated to the recent FB sdk about 2 weeks ago. Here's the way I've done it:
//FB recommends to put these two in the app delegate in their sample apps but you can place them other places
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
NSArray *permissions = [NSArray arrayWithObjects:#"publish_actions", nil];
return [FBSession openActiveSessionWithPermissions:permissions
allowLoginUI:allowLoginUI
completionHandler:^(FBSession *session, FBSessionState state, NSError *error) {
NSLog((#"session.state is %d",session.state));
if(session.state==513){
[AppPrefererences sharedAppPrefererences].faceBookLoggedIn=YES;
NSLog((#"facebookLogin pref has been set to yes from inside appDelegate"));
}
else{
[AppPrefererences sharedAppPrefererences].faceBookLoggedIn=NO;
}
}];
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// FBSample logic
// We need to handle URLs by passing them to FBSession in order for SSO authentication
// to work.
return [FBSession.activeSession handleOpenURL:url];
}
//Then whereever you want to initiate the log in (i use mine in a tableView)
AppDelegate *appDelegate =(AppDelegate*) [[UIApplication sharedApplication]delegate];
if (![appDelegate openSessionWithAllowLoginUI:NO]) {
[appDelegate openSessionWithAllowLoginUI:YES];
facebookLoginLabel.text=#"Facebook Log Out";
[AppPrefererences sharedAppPrefererences].faceBookEnabled=YES;
}
else{
facebookLoginLabel.text=#"Facebook Log In";
[AppPrefererences sharedAppPrefererences].faceBookLoggedIn=NO;
[FBSession.activeSession closeAndClearTokenInformation];
}
And then whererever you want to post you do this:
[FBRequestConnection startForPostStatusUpdate:#"any thing you want to post"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error){}];
And, also don't forget to set the FBLoginViewDelegate
Let me know if you have any questions! Hope it helps!