Cannot modify user Parse - swift

Trying to save logged in parse user's value, it only works for the first time but when i close the app and reopen it, it doesn't work again.
This is the save code I'm using which seems alright
PFUser.current()["about"] = textfield.text
PFUser.current().saveInBackground()
and this is the error i get when trying to save the objects to current user.
PFKeychainStore failed to set object for key 'currentUser', with error: -34018
or
cannot modify user objectIDxx
This started happening after i installed parse server instead of parse.com

Were you using "revocable sessions" before? If not, parse-server requires you to use them. You can check out the migration tutorial here.
You'll need to add this after you initialize parse:
[PFUser enableRevocableSessionInBackground]
And then you will need to re-login a user if you get an 'invalid session' error from parse.
// Swift
class ParseErrorHandlingController {
class func handleParseError(error: NSError) {
if error.domain != PFParseErrorDomain {
return
}
switch (error.code) {
case kPFErrorInvalidSessionToken:
handleInvalidSessionTokenError()
... // Other Parse API Errors that you want to explicitly handle.
}
private class func handleInvalidSessionTokenError() {
//--------------------------------------
// Option 1: Show a message asking the user to log out and log back in.
//--------------------------------------
// If the user needs to finish what they were doing, they have the opportunity to do so.
//
// let alertView = UIAlertView(
// title: "Invalid Session",
// message: "Session is no longer valid, please log out and log in again.",
// delegate: nil,
// cancelButtonTitle: "Not Now",
// otherButtonTitles: "OK"
// )
// alertView.show()
//--------------------------------------
// Option #2: Show login screen so user can re-authenticate.
//--------------------------------------
// You may want this if the logout button is inaccessible in the UI.
//
// let presentingViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
// let logInViewController = PFLogInViewController()
// presentingViewController?.presentViewController(logInViewController, animated: true, completion: nil)
}
}
// In all API requests, call the global error handler, e.g.
let query = PFQuery(className: "Object")
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// Query Succeeded - continue your app logic here.
} else {
// Query Failed - handle an error.
ParseErrorHandlingController.handleParseError(error)
}
}

Related

GIDSignIn current user is nil after restorePreviousSignIn()

My issue here is that: This shared instance is called
GIDSignIn.sharedInstance.restorePreviousSignIn()
But when I try to check the current user it remains nil after the call.
GIDSignIn.sharedInstance.currentUser != nil
I would assume I may need a proper delay before checking if the user is nil or not. How can I properly add a delay or populate the user before checking if the user is nil.
ex: confirming that GIDSignIn.sharedInstance.restorePreviousSignIn() has finished before checking GIDSignIn.sharedInstance.currentUser
if(GIDSignIn.sharedInstance.hasPreviousSignIn() && GIDSignIn.sharedInstance.currentUser == nil) {
GIDSignIn.sharedInstance.restorePreviousSignIn()
}else{
print("user has no shared instance. User has not been prev signed in")
}
if (GIDSignIn.sharedInstance.currentUser != nil) {
}else{
print("user is nil")
}
Using GoogleSignIn SDK
GoogleSignIn Framework Reference
If you studied at the documentation you linked you'd be able to see the restorePreviousSiginin is asynchronous and also know how to use it.
func restorePreviousSignIn(callback: GIDSignInCallback? = nil)
callback
The GIDSignInCallback block that is called on completion. This block will be called asynchronously on the main queue.
where the completion handler is defined as:
typealias GIDSignInCallback = (GIDGoogleUser?, Error?) -> Void
You should use the completion handler to process the result of trying to restore the previous sign in, not try to do it synchronously in the method. Something like
if(GIDSignIn.sharedInstance.hasPreviousSignIn() && GIDSignIn.sharedInstance.currentUser == nil) {
GIDSignIn.sharedInstance.restorePreviousSignIn() {
user, error in
if let user = user {
print(user) // handle the user info
} else {
print(error ?? "unknown error")
}
}
}

How to check if email already exist before creating an account (Swift)

I know different variations of this question have been asked. However I seem to keep running into the same issue every time.
I want to check if an email already exist before the user pushes onto the next view. I will enter an email that exist in the database and the performSegue func is always called and pushes the user as if that email does not exist.
The only way I can check officially is when the user reaches the final sign up VC and the Auth.auth().createUser(withEmail: email as! String, password: password as! String ) { (user, error) in code will check for all errors.
However for good user experience I would hate for the user to have to click back three times to change the email address. Here is the code I have for the enter email view controller.
// Check if email is already taken
Auth.auth().fetchSignInMethods(forEmail: emailTextField.text!, completion: { (forEmail, error) in
// stop activity indicator
self.nextButton.setTitle("Continue", for: .normal)
self.activityIndicator.stopAnimating()
if let error = error {
print("Email Error: \(error.localizedDescription)")
print(error._code)
self.handleError(error)
return
} else {
print("Email is good")
self.performSegue(withIdentifier: "goToCreateUsernameVC", sender: self)
}
})
First off am I even entering the create property in the forEmail section? I added emailTextField.text because its the only way I know how even get the email the user typed. Does anyone know a better way I can do this?
How I create user accounts
This is an example of what I use. When a user provides credentials, FirebaseAuth checks if these credentials can be used to make a user account. The function returns two values, a boolean indicating whether the creation was successful, and an optional error, which is returned when the creation is unsuccessful. If the boolean returns true, we simply push to the next view controller. Otherwise, we present the error.
func createUserAcct(completion: #escaping (Bool, Error?) -> Void) {
//Try to create an account with the given credentials
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordConfirmTextField.text!) { (user, error) in
if error == nil {
//If the account is created without an error, then we will make a ProfileChangeRequest, i.e. update the user's photo and display name.
if let firebaseUser = Auth.auth().currentUser {
let changeRequest = firebaseUser.createProfileChangeRequest()
//If you have a URL for FirebaseStorage where the user has uploaded a profile picture, you'll pass the url here
changeRequest.photoURL = URL(string: "nil")
changeRequest.displayName = self.nameTextField.text!
changeRequest.commitChanges { error in
if let error = error {
// An error happened.
completion(false, error)
} else {
//If the change is committed successfully, then I create an object from the credentials. I store this object both on the FirebaseDatabase (so it is accessible by other users) and in my user defaults (so that the user doesn't have to remotely grab their own info
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
//Store the object as data in my user defaults
let data = NSKeyedArchiver.archivedData(withRootObject: userData)
UserDefaults.standard.set(data, forKey: "UserData")
UserDefaults.standard.set([Data](), forKey: "UserPhotos")
completion(true, nil)
}
}
}
} else {
// An error happened.
completion(false, error)
}
}
}
Here is an example of how I would use it. We can use the success boolean returned to determine if we should push to the next view controller, or present an error alert to the user.
createUserAcct { success, error in
//Handle the success
if success {
//Instantiate nextViewController
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let nextVC = storyboard.instantiateViewController(withIdentifier: "NextVC") as! NextViewController
//Push typeSelectVC
self.navigationController!.pushViewController(viewController: nextVC, animated: true, completion: {
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
})
} else {
//We now handle the error
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
//Create a UIAlertController with the error received as the message (ex. "A user with this email already exists.")
let alertController = UIAlertController(title: "Error", message: error!.localizedDescription, style: .alert)
let ok = UIAlertAction(title: "OK", style: .cancel, action: nil)
//Present the UIAlertController
alertController.addAction(ok)
self.present(alertController, animated: true, completion: nil)
}
}
Let me know if this all makes sense, I know there is a lot to it. I'm just considering things you'll maybe find you need done anyways that you may not be aware of (like making change requests, or storing a data object on FirebaseDatabase).
Now for checking if the email is already taken:
Remember when I said that I post a user object to FirebaseDatabase upon account creation? Well we can query for the given email to see if it already exists. If it doesn't we continue with the flow as normal, without having actually created the account. Otherwise, we simply tell the user to pick another email address.
Pushing a user object to your database (taken from the above code):
if let firebaseUser = Auth.auth().currentUser {
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
}
And now querying to see if somebody already has that email:
func checkIfEmailExists(email: String, completion: #escaping (Bool) -> Void ) {
Database.database().reference().child("Users").queryOrdered(byChild: "email").queryEqual(toValue: email).observeSingleEvent(of: .value, with: {(snapshot: DataSnapshot) in
if let result = snapshot.value as? [String:[String:Any]] {
completion(true)
} else {
completion(false)
}
}
}
Then we can call this like so:
checkIfEmailExists(email: emailTextField.text!, completion: {(exists) in
if exists {
//Present error that the email is already used
} else {
//Segue to next view controller
}
})

Signing Out of Firebase in Swift

I am attempting to sign out of the Firebase API, but I can't seem to figure out how to handle any errors that may occur.
The Firebase pod provides a method for signing out:
FIRAuth.auth()?.signOut()
It is marked with throws, so I have wrapped it in a do/try/catch block in a method to test the signing out process:
do {
try FIRAuth.auth()?.signOut()
} catch (let error) {
print((error as NSError).code)
}
I see that the signOut method is marked with throws in the Firebase pod, but I don't see how it can handle any errors asynchronously. I have tried entering Airplane Mode, which triggers a network error in my code everywhere else that a network request takes place, but with the signOut method, that error isn't caught because I have no completion handler to execute from. All of the other authentication methods from the Firebase pods have a completion handler, in which I am able to handle errors.
Here is the documentation for the signOut method from the Firebase pod:
/** #fn signOut:
#brief Signs out the current user.
#param error Optionally; if an error occurs, upon return contains an NSError object that
describes the problem; is nil otherwise.
#return #YES when the sign out request was successful. #NO otherwise.
#remarks Possible error codes:
- #c FIRAuthErrorCodeKeychainError Indicates an error occurred when accessing the keychain.
The #c NSLocalizedFailureReasonErrorKey field in the #c NSError.userInfo dictionary
will contain more information about the error encountered.
*/
open func signOut() throws
Do you have any suggestions for an appropriate way to handle the signing out of a user when I don't have a completion handler that allows me to check for an error?
You can catch the error like this
do
{
try Auth.auth().signOut()
}
catch let error as NSError
{
print(error.localizedDescription)
}
Edited from Milli's answer to add sending user back to initial page of the app.
// log out
func logout(){
do
{
try Auth.auth().signOut()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let IntroVC = storyboard.instantiateViewController(withIdentifier: "IntroVC") as! introVC
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = IntroVC
}
catch let error as NSError
{
print(error.localizedDescription)
}
}
An error is highly unlikely to occur but it's never good to assume anything. By the sound of the documentation, it wipes out your keychain which is the only way you'd be able to log back into your firebase application. From trying logging out of my own firebase app I was surprise that 0 errors occured. Here is the original code.
#IBAction func logOutTapped(_ sender: Any) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
if Utility.hasFacebook {
let login = FBSDKLoginManager()
login.logOut()
}
if Utility.hasTwitter {
Twitter.sharedInstance().sessionStore.logOutUserID((Twitter.sharedInstance().sessionStore.session()?.userID)!)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "LoginVC")
self.present(initialViewController, animated: false)
}
Anyways if you really want a completion handler then here's something I tossed up quickly
func logOut(completion:#escaping(_ errorOccured: Bool) -> Void) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
completion(true)
}
completion(false)
}

Firebase swift ios login system error Assertion failed/Exec_BAD_INSTRUNCTION (code=EXC_i386_INVOP, subcode=0x0)

Im trying to use the built in authentication system in firebase and followed this TuT to do so
Firebase Login Tutorial
I am able to run my app but when i go to login and type in a valid (or invalid) login email and password it crashes with this error in the console:
Assertion failed: (request.URL), function -[FSRWebSocket initWithURLRequest:protocols:queue:andUserAgent:], file /Users/vikrum/dev/git/firebase-client-objc/Firebase/Firebase/Libraries/SocketRocket/FSRWebSocket.m, line 302.
(lldb)
When I don't type anything and hit login it crashes with this error (i have measures to prevent the app from crashing because of this, possiably side affect of the bigger issue):
<pre>fatal error: unexpectedly found nil while unwrapping an Optional value</pre>
The inline errors below are the same w/ both above console errors
Edit: I have noticed that in the pod directory in my app in the framework and then ios folders the contents are (My hierarchy with red error **LINKhttp://i.stack.imgur.com/IHCQw.png) highlighted in red. The directory for the 4 frameworks was iPhoneOS9.0.sdk for some reason i only have a iPhoneOS.sdk and iPhoneOS9.2.sdk also the firebase and the other framework below it have strange directory locations [(They all go to this location **LINKhttp://i.stack.imgur.com/Z6dX5.png) and i cant for the life of me figure out how to fix it (i'm not totally sure this is the error causing my app to crash after I try to log in but its the only error I have seen so it must be)
Edit 2: i've been looking around and every recent tut to make an app as of at least ios 9 has been confusing me because of this, i have seen 2 separate apps tuts where i downloaded the finale project and i wasn't able to run it because in the pod dir(all where for firebase and ios 9.2 when made and had all the frameworks for firebase) it said "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.0.sdk/System/Library/Frameworks/CFNetwork.framework" and i cant change it because when i go to the little dir change button i have to get into the package contents of xcode and it wont let me do that
The inline error when I am redirected
Second files inline error (same)
Here is where i get directed when the app crashes and i get the error(2 places)
import Foundation
import Firebase
let BASE_URL = "https://baseball-pitcher-app.firebaseIO.comΩΩ"
let FIREBASE_REF = Firebase(url: BASE_URL)
var CURRENT_USER: Firebase
{
***let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as! String*** ERROR IN THIS LINE
let currentUser = Firebase(url: "\(FIREBASE_REF)").childByAppendingPath("users").childByAppendingPath(userID)
return currentUser!
}
One thing I did find odd was that the error was in the logout button which is weird because i've never pressed it since the error occurred.
import UIKit
import Firebase
class LoginViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var userUsernameTextField: UITextField!
#IBOutlet weak var userPasswordTextField: UITextField!
#IBOutlet weak var logoutButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.userUsernameTextField.delegate = self;
self.userPasswordTextField.delegate = self;
}
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
if NSUserDefaults.standardUserDefaults().valueForKey("uid") != nil && CURRENT_USER.authData != nil
{
self.logoutButton.hidden = false
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
self.view.endEditing(true)
return false
}
#IBAction func loginButtonTapped(sender: AnyObject) {
let email = self.userUsernameTextField.text
let password = self.userPasswordTextField.text
if email != "" && password != ""
{
FIREBASE_REF.authUser(email, password: password, withCompletionBlock: { error, authData in
if error == nil
{
NSUserDefaults.standardUserDefaults().setValue(authData.uid, forKey: "uid")
print("Logged In :)")
self.logoutButton.hidden = false
}
else
{
print(error)
}
})
}
else
{
let alert = UIAlertController(title: "Error", message: "Enter Email and Password.", preferredStyle: UIAlertControllerStyle.Alert)
let action = UIAlertAction(title: "Ok", style: .Default, handler: nil)
alert.addAction(action)
self.presentViewController(alert, animated: true, completion: nil)
}
}
#IBAction func logoutButtonTapped(sender: AnyObject) {
CURRENT_USER.unauth()**** ERROR IN THIS LINE
NSUserDefaults.standardUserDefaults().setValue(nil, forKey: "uid")
self.logoutButton.hidden = true
}
The userId id is most likely nil, and in general it's better to catch the cases in which is it nil, like this
if let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as? String {
print(userID) //do something with the user id here
} else {
print("The userID was nil.") //avoid the user id.
}
And the reason for the second error is that CURRENT_USER, since it never Auth'd to start with is now nil. Not sure why the button's IBAction is being called but check the actions and outlets in IB.
Also, the
var CURRENT_USER
is a little suspect.
Based on the name of the var, I would expect it to contain the Current User (as maybe a User Class?).
However when called, it returns a Firebase object which contains a path to the current users node (/appPath/users/users node) and no other user data. It doesn't appear to be populated when a user authenticates either.
Not sure if that was the intention or if it's used somewhere else, but doing this
CURRENT_USER.unauth()
may be an issue since it doesn't contain any auth data.

No FB access token upon initial Parse FB login

I am logging into my app using PFFacebookUtils.logInWithPermissions however, the first time I log in the actual login works fine but I get the FB error code = 2500 An active access token must be used to query information about the current user. This results in any graphRequests failing.
If I then recompile the code to the device this error does not occur and all graphRequests work fine. Why is the app not getting an access token upon the first login? Also, if I completely delete the app from the device and reinstall this whole process starts again (error code = 2500 on first attempt).
Also maybe worth noting, this didn't happen with the previous versions of Parse and FB SDKs. I am just starting to move this app into iOS9 and that's when this problem popped up.
Xcode 7.0.1, Parse SDK v1.8.5, FacebookSDKs-iOS-20150910
I was able to resolve this issue by first calling the FBSDK login method followed by the Parse SDK login method (inside the FBSDK callback):
#IBAction func facebookButtonAction(sender: AnyObject) {
// Ensure that PFUser == nil - just in here because I heard it can help with some possible issues
PFUser.logOut()
let permissions = ["public_profile", "email", "user_friends"]
// First, login with FB’s SDK
let login: FBSDKLoginManager = FBSDKLoginManager()
login.logInWithReadPermissions(permissions, handler: {(result: FBSDKLoginManagerLoginResult!, error: NSError!) in
if (error != nil) {
NSLog("Process error")
} else {
if result.isCancelled {
NSLog("Cancelled")
} else {
NSLog("Logged in")
// Then with Parse’s, upon FB login success
PFFacebookUtils.logInWithPermissions(permissions) { (user, 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.")
//...
}
}
}
}
})
}