Search code examples
swiftparse-platformparse-serverpfuserpffacebookutils

How to link existing Parse User via FacebookUtil


I had tried a lot to achieve link existing parse user to Facebook.

I'm using swift 3.

But I can't find solution

My Status is

  1. I have already account not linked Facebook.

  2. I want link my account via facebookUtils

  3. My app has "Sign In or SignUp Facebook" Button

  4. It working like below

My problem is

  1. I didn't login when I tapped Facebook login button. It means I has no PFUser.current()

Here is what I'm trying to do

  1. get email address via facebookGraph API and check isUser or not via query for email

  2. I had tried login like this

try! PFUser.logIn(withUsername: userObj.username!, password: userObj.password!)

but It is not working because I can't get password. It always returned nil

  1. I had tried link PFUser Object which I had get via 1 query results

  2. I also had tried cloud function like this

Parse.Cloud.define("fblink", function(request, response) {

var query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);

query.first({
  success: function(object) {

       if (!Parse.FacebookUtils.isLinked(object)) {
              Parse.FacebookUtils.link(object, null, {
                success: function(user) {
                  alert("Woohoo, user logged in with Facebook!");
                    response.success(true);   
                },
                error: function(user, error) {
                  alert("User cancelled the Facebook login or did not fully authorize.");
                    response.success(false);   
                }
              });
            }
  },
  error: function(error) {
    console.log("Error: " + error.code + " " + error.message);
  }
});

});

But It is not working...I think cloud function approach is not a solution in this case.

I had found a lot of questions related merging exist user to Facebook authdata. But I can't find any solution.

I hope find good way together.

func fbBtnTapped() {

        //Show Activity indicator
        let spiningActivity = MBProgressHUD.showAdded(to: self.view, animated: true)
        spiningActivity?.labelText = "Loading"
        spiningActivity?.detailsLabelText = "Please Wait"


        let permissions = ["public_profile", "email"]


        PFFacebookUtils.logInInBackground(withReadPermissions: permissions, block: {(user:PFUser?, error:Error?) -> Void in

            if let user = user {
                if user.isNew {
                    print("User signed up and logged in through Facebook!")

                } else {
                    print("User logged in through Facebook!")
                }
            } else {
                print("Uh oh. The user cancelled the Facebook login.")

            }


            if(error != nil)
            {
                spiningActivity?.hide(true)

                print("in FBBUTTONTapped Error")
                // display an error message
                let userMessage = error!.localizedDescription
                let myAlert = UIAlertController(title: "Alert", message: userMessage, preferredStyle: UIAlertControllerStyle.alert)

                let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)

                myAlert.addAction(okAction)

                self.present(myAlert, animated: true, completion: nil)
                return
            }

            // Load facebook details like userName, email address, profile picture.
            self.loadFacebookUserDetails()


        })



    }

Here is what I had tried in loadFacebookUserDetails.

func loadFacebookUserDetails()
    {
        MBProgressHUD.hideAllHUDs(for: self.view, animated: true)

        //Show Activity indicator
        let spiningActivity = MBProgressHUD.showAdded(to: self.view, animated: true)
        spiningActivity?.labelText = "Loading"
        spiningActivity?.detailsLabelText = "Wait"


        //Define fields we would like to read from Facebook User Object
        let requestParameters = ["fields": "id, email, first_name, last_name"]
        // me
        let userDetails : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: requestParameters)

        userDetails.start(completionHandler: { (connection, result, error) -> Void in

            if error != nil {


                print("in FBDetailed Error")
                //Display error message
                spiningActivity?.hide(true)

                let userMessage = error!.localizedDescription
                let myAlert = UIAlertController(title:"Alert", message: userMessage, preferredStyle: UIAlertControllerStyle.alert)

                let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)

                myAlert.addAction(okAction)

                self.present(myAlert, animated: true, completion: nil)

                PFUser.logOut()

                return

            }

            var userId = String()
            var userName = String()
            var userEmail = String()
            var userFirstName = String()
            var userLastName = String()
            var displayName = String()

            var userObj = PFUser()

            //Extract user fields
            if let dict = result as? [String: AnyObject]
            {
                userId = dict["id"] as! String
                print("User id=\(userId)")
                userEmail = (dict["email"] as? String)!
                print("User email=\(userEmail)")

                userFirstName = dict["first_name"] as! String
                print("User FirstName=\(userFirstName)")
                userLastName = dict["last_name"] as! String
                print("User LastName=\(userLastName)")
            }

            //get Username
            if !userEmail.isEmpty
            {
                PFUser.current()?.email = userEmail
                if let range = userEmail.range(of: "@") {

                    let username = userEmail.substring(to: range.lowerBound)
                    userName = username

                    PFUser.current()?.username = username
                }
            }

            PFFacebookUtils.linkUser(inBackground: PFUser.current()!, with: FBSDKAccessToken.current())


            //Get Facebook profile picture
            let userProfile = "https://graph.facebook.com/" + userId + "/picture?type=large"

            let profilePictureUrl = URL(string:userProfile)
            let profilePictureData = try? Data(contentsOf: profilePictureUrl!)

            //Prepare PFUser object
            if(profilePictureData != nil)
            {
                let profileFileObject = PFFile(data:profilePictureData!)

                //                userObj.setObject(profileFileObject!, forKey: "profileImg")

                if (PFUser.current()?.object(forKey: "profileImg")) == nil{

                    PFUser.current()?.setObject(profileFileObject!, forKey: "profileImg")
                }
            }

            displayName=userFirstName+userLastName
            PFUser.current()?.setObject(displayName, forKey: "displayname")



            //Check If user has already signedup or not
            let query = PFQuery(className: "_User")
            query.whereKey("username", equalTo: userName)
            try! userObj = query.getFirstObject() as! PFUser

//            if userObj.username != nil {
//                
//                print("already signed up")
//                return
//            }
//            print(FBSDKAccessToken.current())



//            try! PFUser.logIn(withUsername: userObj.username!, password: userObj.password!)

            PFFacebookUtils.linkUser(inBackground: PFUser.current()!, with: FBSDKAccessToken.current())


//            userObj.setObject(displayName, forKey: "displayname")

            PFUser.current()?.saveInBackground(block: { (success:Bool, error:Error?) in
                spiningActivity?.hide(true)

                if(error != nil)
                {
                    let userMessage = error!.localizedDescription
                    let myAlert = UIAlertController(title: "Alert2", message: userMessage, preferredStyle: UIAlertControllerStyle.alert)

                    let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)

                    myAlert.addAction(okAction)

                    self.present(myAlert, animated: true, completion: nil)

                    PFUser.logOut()
                    return
                }

                if(success)
                {
                    if !userId.isEmpty
                    {
                        print(userId)
                        //userId->userName
                        UserDefaults.standard.set(userName, forKey: "username")
                        UserDefaults.standard.synchronize()

                        DispatchQueue.main.async{
                            let appDelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate

                            appDelegate.buildUserInterface()
                        }

                    }

                }
            })
//



        })



    }

Solution

  • To be able to link the account you will need PFUser.current() if he is not a Facebook linked account. Your options are:

    1:) Logs in with facebook via the login page with your logic. This means that you have a new user in the Users column. So essentially that can be used as a parse login regardless later. You can just update the username with maybe email or facebook name ( because facebook login will fill the username with the ID by default ). Once he gets to the success page, you can ask him to fill in a password for later if he chooses to not use his facebook account in future. Then he is welcome to unlink the account in settings page with a unlink button.

    @IBAction func unlinkFacebook(_ sender: AnyObject) {
        let user = PFUser.current()
        showHUD(message: "unlinking")
        PFFacebookUtils.unlinkUser(inBackground: user!, block:{
            (succeeded: Bool?, error) -> Void in
            if succeeded! {
                self.simpleAlert(mess: "Your account is no longer associated with your Facebook account.")
                self.hideHUD()
    
            }else{
                self.simpleAlert(mess: "\(error!.localizedDescription)")
                self.hideHUD()
            }
        })
    }
    

    all the unlinking will do is clear the authData column.

    2:) Logs in as a parse user, once successfully logged in ask him to link his account to facebook. a func maybe like:

    func linkFacebook() {
       let user = PFUser.current()
        showHUD(message: "linking")
        if !PFFacebookUtils.isLinked(with: user!) {
            PFFacebookUtils.linkUser(inBackground:  user!, withReadPermissions: nil, block:{
                (succeeded: Bool?, error) -> Void in
                if succeeded! {
                 self.simpleAlert(mess: "Success. You can now log in using your facebook account in future.")
                 self.hideHUD()
                }else{
                 self.simpleAlert(mess: "\(error!.localizedDescription)")
                 self.hideHUD()
                }
            })
        }
    }
    

    Could put that in your viewdidLoad or a nice button that gives the user a choice if he chooses to want to link his account. bare in mind you will need to call the Facebook sdk to update the user details if you want to store the Facebook details.

    Only column that is populated in the Users table is authData.