I have an app that saves multiple user accounts for retrieving data from my API. There is always one account set to be active or flagged TRUE. When I select a different account I deactivate previously active account and activate selected account or set it to be TRUE. I do these using CoreData in my Accounts table view.
When my app starts my API class checks my CoreData to see which account is active and assigns its credentials to my Credential object.
I then use these credentials to access and get data from my server. All works perfect until this point.
My problem is when I select a different account and reset my Credentials object in my didSelectRowAtIndexPath method, my API class still has the previously active account's credentials. I can see/print the Credentials object has the selected account's credentials.
How do I adjust my objects to have/use only selected account's credentials?
class API {
var credentials:Credentials!
init(){
self.credentials = getActiveStore()
// I use self.credentials.username to make API calls later in the code
}
func getActiveStore() -> Credentials?{
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName: "User")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = NSPredicate(format: "active = %#", 1 as NSNumber)
var error:NSError?
let fetchResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as! [User]
if fetchResults.count == 1 {
let name = fetchResults[0].name
let url = fetchResults[0].url
let username = fetchResults[0].username
let password = fetchResults[0].password
return Credentials(name: name, url: url, username: username, password: password)
}
return nil
}
}
Now this is the class that never changes. When I refresh my data after selecting a different account, the data I see belongs to previously active account. API class doesn't want to change.
Are you creating a new credentials every time you select a new cell? Because you credentials gets update just in the criation of the object
init(){
self.credentials = getActiveStore()
// I use self.credentials.username to make API calls later in the code
}
If you want credentials to be update a new instance of your API has to be created.
Another way is to create an update method and call it when a new cell is selected
func updateCredentials(){
self.credentials = getActiveStore()
// I use self.credentials.username to make API calls later in the code
}
Related
I'm implementing login with Facebook using Firebase, I have this code which searches my database after a successful facebook authentication for the email if exists in database and logs in the app if found, I want to direct the user to registration view controller if not found but its not working since this method is asynchronous. I appreciate if anyone can help. Here is my code :
func getFacebookUserInfo() {
if(FBSDKAccessToken.current() != nil){
let graphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields" : "id,name,gender,email,education"])
let connection = FBSDKGraphRequestConnection()
connection.add(graphRequest, completionHandler: { (connection, result, error) -> Void in
let data = result as! [String : AnyObject]
let email = data["email"] as? String
let emailRef = FIRDatabase.database().reference().child("usernameEmailLink")
emailRef.queryOrderedByValue().observe(.childAdded, with: { snapshot in
if let snapshotValue = snapshot.value as? [String: AnyObject] {
for (key, value) in snapshotValue {
if(value as? String == email){
self.stringMode = snapshotValue["mode"]! as! String
self.username = key
self.parseUserInfoFromJSON()
return
}
}
}
})
})
connection.start()
}
}
Thank you.
The registration/existence of the user in Firebase should probably be determined before the graphRequest code in the question.
Most importantly, (and this is critical), email addresses are dynamic so they should not be used to verify if a user exists. i.e. user with email address of 'leroy#gmail.com' updates his email to 'leroy.j#gmail.com'. If emails are used to verify registration, it can totally break if that email changes.
Please use Firebase uid's for that purpose as they are static and unique.
Since we only have a small snippet of code, we don't know the exact sequence being used. This answer is pseudo-code to outline a possible sequence.
We assume that by 'registered' it means that the user has gone through some kind of app registration sequence and the user has been created (and now exists/is registered) in Firebase.
In general there would be a login button and a delegate method to handle the actual login action.
The user enters their login and taps the login button
func loginButton(loginButton: FBSDKLoginButton!,
didCompleteWithResult result: FBSDKLoginManagerLoginResult!,
error: NSError?) {
Firebase can then get the credentials for that user (see Firebase doc quote below)
let credential = FIRFacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
At that point, sign in the user and check to see if they are registered (exist) in the Firebase user node.
FIRAuth.auth()?.signIn(with: credential) { (user, error) in
if let error = error { //failed due to an error
return
}
let uid = user.uid //the firebase uid
let thisUserRef = userRef.child(uid) //a reference to the user node
//check to see if the user exists in firebase (i.e. is Registered)
thisUserRef.observeSingleEvent(of: .value, with: { (snapshot) in
//if snapshot exists
//then the user is already 'registered' in the user node
// so continue the app with a registered user
//if not, then need to have the user go through a registration sequence and
// then create the user (make them registered) in the user node
doRegisterUser(user)
})
func doRegisterUser(user: FIRUser) {
//get what you need from the user to register them
// and write it to the users node. This could be from additional
// questions or from their Facebook graph, as in the code in the
// question
//for this example, we'll just write their email address
let email = user.email
let dict = ["email": email]
//create a child node in the users node with a parent of uid
// and a child of email: their email
thisUserRef.setValue(node)
//next time the user logs in via FB authentication, their user node
// will be found as they are now a 'registered' user
}
From the Firebase docs
After a user signs in for the first time, a new user account is
created and linked to the credentials—that is, the user name and
password, or auth provider information—the user signed in with. This
new account is stored as part of your Firebase project, and can be
used to identify a user across every app in your project, regardless
of how the user signs in.
As I mentioned, this is very pseudo code but offers a possible sequence for a solution.
I have an issue when the Realm Object Server have a different IP. The application can login through by Credential but after that it will return empty data although my database sit right on that IP and can be accessed by Realm Browser. Actually, I only use one account in realm object server and I create a user table with username and password so that after it can connect through Credential to the server, I will read the username and password on screen and check it information in database.
Connect to Realm Object Server function:
class func login(username: String, password: String, action: AuthenticationActions, completionHandler: #escaping ()->()) {
let serverURL = NSURL(string: realmIP)!
let credential = Credential.usernamePassword(username: username, password: password, actions: [action])
SyncUser.authenticate(with: credential, server: serverURL as URL) { user, error in
if let user = user {
syncUser = user
let syncServerURL = URL(string: realmURL)!
let config = Realm.Configuration(syncConfiguration: (user, syncServerURL))
realm = try! Realm(configuration: config)
} else if error != nil {
}
completionHandler()
}
}
Query from table after login by SyncUser:
class func loginLocal(employee: String) -> Bool{
let predicate = NSPredicate(format: "employee = %#", employee)
if (realm != nil) {
let user = realm?.objects(MyUser.self).filter(predicate)
if ((user?.count)! > 0) {
return true
}
}
return false
}
The solution seems to be weird so that I have to call a function multiple times by pressing my login button and hope that it will go through to the server.
This is my first application using Realm and Realm Object Server so I don't have much experience in this situation.
I may need more information on how you're handling the logged in Realm after the login, but from the code you've shown there, it looks like you're accidentally accessing a local version of the Realm and not the synchronized one.
Once logged in, you need to make sure you use the same Configuration object whenever you create Realm instances after that. It's not recommended to create and then save the realm instance inside the login completion block, as this block occurs on a background thread, making it unavailable anywhere else.
If your app is always online, it's easier to simply set the sync configuration as the default Realm for your app:
SyncUser.authenticate(with: credential, server: serverURL as URL) { user, error in
if let user = user {
syncUser = user
let syncServerURL = URL(string: realmURL)!
let config = Realm.Configuration(syncConfiguration: (user, syncServerURL))
Realm.Configuration.defaultConfiguration = config
}
completionHandler()
}
Otherwise, you can either save the Configuration in some sort of global object, or recreate it each time you need to create a Realm instance. The important thing to remember is you need to make sure your Realm instance is using a Configuration object with the successfully logged in user, otherwise it will default back to using a normal, empty local Realm.
I would like my user to add/edit details about their profile after they register with my app.
#IBAction func doneEditting(sender: AnyObject) {
self.completeEdit()
}
func completeEdit() {
var user = PFUser()
user["location"] = locationTextField.text
user.saveInBackgroundWithBlock {
(succeeded: Bool, error: NSError?) -> Void in
if let error = error {
let errorString = error.userInfo?["error"] as? NSString
println("failed")
} else {
self.performSegueWithIdentifier("Editted", sender: nil)
}
}
}
the breakpoint stops right at user.saveInBackgroundWithBlock. No of the docs show how to append new columns after the signup.
Thanks!
You are mentioning that the user should be able to edit their profile after they have registered. When registering a user with Parse using signUpInBackgroundWithBlock, then the Parse SDK will automatically create a PFUser for you.
In your provided code you are creating and saving a completely new PFUser instead of getting the one which is currently logged in. If you are not using the PFUser which is logged in, then you will get the following error at user.saveInBackgroundWithBlock (which you are also mentioning in your post):
User cannot be saved unless they are already signed up. Call signUp first
To fix this, you will need to change:
var user = PFUser()
To the following:
var user = PFUser.currentUser()!
The rest of your code (for example user["location"] = locationTextField.text) works fine and will dynamically/lazily add a new column to your User database (which is what you want).
Parse allows you to add columns to a class lazily, meaning that you can add a field to your PFObject and if it is not present in your Parse class, Parse will add that column for you.
Here's example how you would add a column via code:
// Add the new field to your object
yourObject["yourColumnName"] = yourValue
yourObject.saveInBackground()
You'll notice that Parse will create a new column named yourColumnName on their web portal.
reference from HERE.
I have an app that can save multiple accounts to access my server API and retrieve data. Each account has its own credentials so the data pulled from server changes depending on which account is selected.
When my app runs first time, my API class checks my CoreData model to see which account is active and uses that account's credentials throughout the app. I can select a different account and set it as active and unset/inactivate all other accounts in my Accounts tableView and reset the Credentials object with the new selected account, but when I do my app still uses the account that the app first started with.
How do I structure my class to use the account's credentials that I select in my Accounts tableView? Below is my API class I use. This class is called when my app starts.
class API {
init(){
self.credentials = getActiveStore()
// I then use self.credentials to make API calls.
// Rest of the code is omitted for brevity
}
func getActiveStore() -> Credentials?{
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName: "User")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = NSPredicate(format: "active = %#", 1 as NSNumber) //NSPredicate(format: "active == YES", argumentArray: nil)
var error:NSError?
let fetchResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as! [WooStore]
if fetchResults.count == 1 {
let name = fetchResults[0].name
let url = fetchResults[0].url
let username = fetchResults[0].username
let password = fetchResults[0].password
return Credentials(name: name, url: url, username: username, password: password)
}
return nil
}
}
I would just use the NSManagedObject that is returned as your credentials object. There is no reason to create a new object that is just a copy of the NSManagedObject.
Subclass NSManagedObject, add any convenience methods that you want to the subclass and return it.
Update
My User is an NSManagedObject class which doesn't have an initializer
All objects have an initializer. Even NSManagedObject.
However, you don't need to initialize the object because it is already initialized and populated! It is sitting in your fetchResults array.
if let cred = fetchResults.lastObject {
return cred
} else {
return nil
}
Instead of referencing the first object in the array over and over again just to construct some other object.
Update
and I still don't see the effect I want to have. The data still belongs to previous user after I selected different account.
Based on your code, you determine the active user based on a boolean flag in the persistent store. While this is not a good idea, lets roll with it.
To change the user, you would need to set the current user's flag to 0. Then you need to fetch the soon to be active user and set their flag to 1. Then save the context so that the changes are persisted in the store. Then this code will do what you expect it to do.
You would do all of that wherever in your app you are switching users. NOT in the code you have provided here.
Struggling to get this working, lack of understanding, but if anyone could help me to get there, that would be great. Currently I have a user logging in as an administrator by just setting a boolean, this works fine:
if (authority == "true"){
let acl = PFACL(user: PFUser.currentUser()) // Only user can write
acl.setPublicReadAccess(true) // Everybody can read
acl.setWriteAccess(true, forUser: PFUser.currentUser()) // Also
var role:PFRole = PFRole(name: "Administator", acl: acl)
role.users.addObject(PFUser.currentUser())
role.saveInBackground()
self.activityIndicator.stopAnimating()
let vc : AnyObject! = self.storyboard?.instantiateViewControllerWithIdentifier("Admin")
self.showViewController(vc as UIViewController, sender: vc)
}
This sets an admin role in my parse database. What I am trying to do is to give privileges to this user so that he could edit all other users e.g. giving points to the students for the correctly completed tasks.
if let object = userObject?{
println(tasksCorrect)
object["tasksCorrect"] = tasksCorrect + 1
println(object)
object.saveInBackground()}
You need to ensure that user objects have an ACL that includes read/write access for the "Administrator" role.