I migrated my iOS code to FBSDK v4 + Parse 1.7.1 and I'm trying to handle the case of linking a user to a FB ID but it's already linked:
Another user is already linked to this facebook id. (Code: 208, Version: 1.7.1)
Everything seems to work fine after user logout and FB login but every time I alter or save the current user, or query a table, I get various errors such as:
Parse::UserCannotBeAlteredWithoutSessionError (Code: 206, Version: 1.7.1)
"PFKeychainStore" failed to set object for key 'currentUser'
no results matched the query (Code: 101, Version: 1.7.1)
Here is my code:
NSArray *permisions = [NSArray arrayWithObjects:#"public_profile", #"email", #"user_friends", nil];
[PFFacebookUtils linkUserInBackground:[PFUser currentUser] withReadPermissions:permissions block:^(BOOL succeeded, NSError *error) {
if(succeeded && !error) {
//... use data
}
else {
if(error.code == 208) {
[PFUser logOut];
[PFFacebookUtils logInInBackgroundWithReadPermissions:permissions block:^(PFUser *user, NSError *error) {
//here I get a valid user and no error but...
//after this point the errors mentioned start to show up
}
}
else
//... handle other errors
}
}];
I use Parse's automatic user until a user is linked to a FB account.
Would love some help about this, thanks!
Adding becomeInBackground stopped the error:
NSArray *permisions = [NSArray arrayWithObjects:#"public_profile", #"email", #"user_friends", nil];
[PFFacebookUtils linkUserInBackground:[PFUser currentUser] withReadPermissions:permissions block:^(BOOL succeeded, NSError *error) {
if(succeeded && !error) {
//... success
}
else {
if(error.code == kPFErrorFacebookAccountAlreadyLinked) {
[PFUser logOutInBackgroundWithBlock:^(NSError *error) {
if(!error) {
[PFFacebookUtils linkUserInBackground:[PFUser currentUser] withReadPermissions:permissions block:^(BOOL succeeded, NSError *error) {
[PFUser becomeInBackground:[[PFUser currentUser] sessionToken] block:^(PFUser *user, NSError *error) {
if(user && !error) {
// ... success
}
else {
//... handle become error
}
}];
}];
}
else {
// ... handle bad logout
}
}
else {
//... handle other errors
}
}
}];
I tried over 2000 things to get the user's email. I can't get it from the Facebook SDK's graph API. It doesn't contain email property. I also tried to add manually the email property to the FB framework and nothing happened. Is it possible to download the first FB SDK which is compatible with iOS 7? Does it still have the email property, doesn't it? Or is there any other way how to get the REAL email, I must work with them. I don't need the example#facebook.com.
Thanks for any advice.
EDIT
NSArray *permissions = #[#"email", #"public_profile"];
[PFFacebookUtils logInWithPermissions:permissions block:^(PFUser *user, NSError *error) {
if (!user) {
if (!error) {
NSLog(#"The user cancelled the Facebook login.");
} else {
NSLog(#"An error occurred: %#", error.localizedDescription);
}
if ([delegate respondsToSelector:#selector(commsDidLogin:)]) {
[delegate commsDidLogin:NO];
}
} else {
if (user.isNew) {
NSLog(#"User signed up and logged in through Facebook!");
} else {
NSLog(#"User logged in through Facebook!");
}
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
PFUser *usere = [PFUser currentUser];
[usere setObject:[result objectForKey:#"first_name"] forKey:#"name"];
[usere setObject:[result objectForKey:#"last_name"] forKey:#"surname"];
// [usere setObject:[result objectForKey:#"email"] forKey:#"mail"];
[usere saveEventually];
NSLog(#"user info: %#", result);
}
}];
}
if ([delegate respondsToSelector:#selector(commsDidLogin:)]) {
[delegate commsDidLogin:YES];
}
}];
}
I have not code for graph API,
but with new facebook sdk version 3, I have code for that.
-(void)openFbSession
{
[[self appDelegate].session closeAndClearTokenInformation];
NSArray *permissions = [NSArray arrayWithObjects:#"email",#"user_location",#"user_birthday",#"user_hometown",nil];
[self appDelegate].session = [[FBSession alloc] initWithPermissions:permissions];
[[self appDelegate].session openWithCompletionHandler:^(FBSession *session,
FBSessionState status,
NSError *error) {
if(!error)
{
NSLog(#"success");
[self myFbInfo];
}
else
{
NSLog(#"failure");
}
}];
}
and for all information, myFbInfo method is
-(void)myFbInfo
{
[FBSession setActiveSession:[self appDelegate].session];
[[FBRequest requestForMe] startWithCompletionHandler:^(FBRequestConnection *connection, NSDictionary<FBGraphUser> *FBuser, NSError *error) {
if (error) {
// Handle error
}
else {
//NSString *userName = [FBuser name];
//NSString *userImageURL = [NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large", [FBuser id]];
NSLog(#"Name : %#",[FBuser name]);
NSLog(#"first name : %#",[FBuser first_name]);
NSLog(#"Last name : %#",[FBuser last_name]);
NSLog(#"ID : %#",[FBuser id]);
NSLog(#"username : %#",[FBuser username]);
NSLog(#"Email : %#",[FBuser objectForKey:#"email"]);
NSLog(#"user all info : %#",FBuser);
}
}];
}
EDIT
in appdelegate.h
#property (strong, nonatomic) FBSession *session;
in appdelegate.m
- (BOOL)application: (UIApplication *)application openURL: (NSURL *)url sourceApplication: (NSString *)sourceApplication annotation: (id)annotation
{
//NSLog(#"FB or Linkedin clicked");
return [self.session handleOpenURL:url];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[FBSession.activeSession handleDidBecomeActive];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
[self.session close];
}
You have to request permissions first, as descibed at https://developers.facebook.com/docs/facebook-login/ios/v2.0#button-permissions
After that, you can request the user's information: https://developers.facebook.com/docs/ios/graph#userinfo
You can't just get the user's email address, you must ask them for permission to do so. Take a look at this:
https://developers.facebook.com/docs/facebook-login/permissions/v2.0
Facebook Connect will allow the passing of scope=email in the get string of your auth call.
I could not get it to work with above examples. I solved it using a more complex call to FBSDKGraphRequest..
In viewDidLoad:
if(FBSDKAccessToken.currentAccessToken() != nil) {
println("Logged in to FB")
self.returnUserData() //Specified here below
} else {
print("Not logged in to FB")
let loginView : FBSDKLoginButton = FBSDKLoginButton()
loginView.center = self.view.center
loginView.readPermissions = ["public_profile", "email", "user_friends"]
loginView.delegate = self
self.view.addSubview(loginView)
}
}
Read permissions above is important to be able to get it later on when you request from FB server.
Remember to conform to the "FBSDKLoginButtonDelegate" protocols by including the functions needed (not included here).
To be able to fetch email etc. I use the more complex call for the graphRequest and specify the accesstoken and the parameters (se below).
let fbAccessToken = FBSDKAccessToken.currentAccessToken().tokenString
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(
graphPath: "me",
parameters: ["fields":"email,name"],
tokenString: fbAccessToken,
version: nil,
HTTPMethod: "GET")
... and in the same function execute with completionHandler:
graphRequest.startWithCompletionHandler({ (connection, result, error) -> () in result
if ((error) != nil) {
// Process error
println("Error: \(error)")
} else {
println("fetched user: \(result)")
}
}
Works beautifully!
And additionally.. parameters are found listed here:
https://developers.facebook.com/docs/graph-api/reference/v2.2/user
if ([result.grantedPermissions containsObject:#"email"]) {
// Do work
NSLog(#"%#",[FBSDKAccessToken currentAccessToken]);
if ([FBSDKAccessToken currentAccessToken]) {
[[[FBSDKGraphRequest alloc] initWithGraphPath:#"/me" parameters:[NSMutableDictionary dictionaryWithObject:#"picture.type(large),id,email,name,gender" forKey:#"fields"] tokenString:result.token.tokenString version:nil HTTPMethod:#"GET"]
startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSLog(#"fetched user:%#", result);
}
}];
}
}
I am in trouble with parse user`s email. I can take from user almost everything though fb graph api. But it does not contain email property. Any idea, how to get it? If I do NSlog it always returns null.
{
NSArray *permissionsArray = #[#"email", #"basic_info"];
[PFFacebookUtils logInWithPermissions:permissionsArray block:^(PFUser *user, NSError *error) {
// Was login successful ?
if (!user) {
if (!error) {
NSLog(#"The user cancelled the Facebook login.");
}else {
NSLog(#"An error occurred: %#", error.localizedDescription);
}
// Callback - login failed
if ([delegate respondsToSelector:#selector(commsDidLogin:)]) {
[delegate commsDidLogin:NO];
}
}else if (user.isNew) {
NSLog(#"User signed up and logged in through Facebook!");
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSDictionary<FBGraphUser> *prop = (NSDictionary<FBGraphUser> *)result;
NSDictionary *userData = (NSDictionary *)result;
[[PFUser currentUser] setObject:prop.id forKey:#"fbid"];
[[PFUser currentUser] saveInBackground];
[[PFUser currentUser] setObject:prop.first_name forKey:#"firstname"];
[[PFUser currentUser] saveInBackground];
[[PFUser currentUser] setObject:prop.last_name forKey:#"lastname"];
[[PFUser currentUser] saveInBackground];
NSString *mail = userData[#"email"];
[[PFUser currentUser] setObject:mail
forKey:#"email"];
[[PFUser currentUser] saveInBackground];
NSLog(#"prop %#", prop);
}else {
NSLog(#"User logged in through Facebook!");
NSLog(#"Welcome Screen I am %#", [[PFUser currentUser] username]);
}
}];
}
else {
//HERE
NSLog(#"Error getting the FB username %#", [error description]);
}
[[PFUser currentUser] saveInBackground];
// Callback - login successful
if ([delegate respondsToSelector:#selector(commsDidLogin:)]) {
[delegate commsDidLogin:YES];
}
}];
}
Not every Facebook user exposes the same information as part of their public profile, the email field is one in particular where you need to accept that this will often NOT be exposed.
The userData NSDictionary you get back will only be populated with information the person has made public, so handle missing keys as needed per user.
Documentation to read:
Requesting permissions
Reading the user's information
After logging in with Facebook SDK. I am able to create a new PFUser but the e-mail address of this user does not save in my Databrowser. How can I get this done?
Action for the button:
- (IBAction)didTapFb:(id)sender {
[activityIndicator startAnimating];
PFUser *user = [PFUser currentUser];
if (![PFFacebookUtils isLinkedWithUser:user]) {
[PFFacebookUtils linkUser:user permissions:nil block:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(#"Woohoo, user logged in with Facebook!");
}
}];
NSArray *permissionsArray = #[ #"user_about_me", #"user_relationships", #"user_birthday", #"user_location", #"email"];
[PFFacebookUtils logInWithPermissions:permissionsArray block:^(PFUser *user, NSError *error) {
if (!user) {
if (!error) {
NSLog(#"Uh oh. The user cancelled the Facebook login.");
} else {
NSLog(#"Uh oh. An error occurred: %#", error);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:#"Error en conexión." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
} else if (user.isNew) {
NSLog(#"User with facebook signed up and logged in!");
[self performSegueWithIdentifier:#"MaintoLook" sender:self];
} else {
NSLog(#"User with facebook logged in!");
[self performSegueWithIdentifier:#"MaintoLook" sender:self];
[activityIndicator stopAnimating];
}
}];
}
}
I am giving to my users the options to sign up manually or using Facebook(I am using Parse). The problem now is that if a user has signup manually and then try to login with Facebook is creating a second account and leaves the email valun to null(becuase already exists). So what is the solution for this one? Is there any official solution? I tried a workaround:
FBRequest *requestEmail = [FBRequest requestForMe];
[requestEmail startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
NSLog(#"Error %#",error);
NSDictionary *userData2 = (NSDictionary *)result;
NSString *email2=userData2[#"email"];
PFQuery *query = [PFUser query];
[query whereKey:#"email" equalTo:email2];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
NSString *emailQue =[object objectForKey:#"email"];
if(error || (emailQue==NULL) || (emailQue==nil)){
NSLog(#"User facebook account does not exist");
}
else{
NSLog(#"User facebook email exist");
NSLog(#"email %#",emailQue);
}
}];
}];
which is failing (No active token available) and I believe this is not the correct way. Even if this work then I cant stop the loginWithPermission to create the new user once canceled.
Any suggestions?
If you know that this user is an existing PFUser you can instead link their Facebook account with their existing account. This is a supported Parse feature.
if (![PFFacebookUtils isLinkedWithUser:user]) {
[PFFacebookUtils linkUser:user permissions:nil block:^(BOOL succeeded, NSError *error) {
if (succeeded) {
NSLog(#"Woohoo, user logged in with Facebook!");
}
}];
}
You can get the current user with
PFUser *currentUser = [PFUser currentUser];
Or you could even convert an Anonymous user to a Facebook user.
https://parse.com/docs/ios_guide#fbusers-link/iOS