How to reset and toggle API credentials - ios

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.

Related

How to get ObjectID and search for specific ObjectID in CoreData in Swift 5?

I am currently working on a project with a multi user system. The user is able to create new profiles which are saved persistently using CoreData.
My problem is: Only one profile can be the active one at a single time, so I would like to get the ObjectID of the created profile and save it to UserDefaults.
Further I was thinking that as soon as I need the data of the active profile, I can simply get the ObjectID from UserDefaults and execute a READ - Request which only gives me back the result with that specific ObjectID.
My code so far for SAVING THE DATA:
// 1. Create new profile entry to the context.
let newProfile = Profiles(context: context)
newProfile.idProfileImage = idProfileImage
newProfile.timeCreated = Date()
newProfile.gender = gender
newProfile.name = name
newProfile.age = age
newProfile.weight = weight
// 2. Save the Object ID to User Defaults for "activeUser".
// ???????????????????
// ???????????????????
// 3. Try to save the new profile by saving the context to the persistent container.
do {
try context.save()
} catch {
print("Error saving context \(error)")
}
My code so far for READING THE DATA
// 1. Creates an request that is just pulling all the data.
let request: NSFetchRequest<Profiles> = Profiles.fetchRequest()
// 2. Try to fetch the request, can throw an error.
do {
let result = try context.fetch(request)
} catch {
print("Error reading data \(error)")
}
As you can see, I haven't been able to implement Part 2 of the first code block. The new profile gets saved but the ObjectID isn't saved to UserDefaults.
Also Party 1 of the second code block is not the final goal. The request just gives you back all the data of that entity, not only the one with the ObjectID I stored in User Defaults.
I hope you guys have an idea on how to solve this problem.
Thanks for your help in advance guys!
Since NSManagedObjectID does not conform to one of the types handled by UserDefaults, you'll have to use another way to represent the object id. Luckily, NSManagedObjectID has a uriRepresentation() that returns a URL, which can be stored in UserDefaults.
Assuming you are using a NSPersistentContainer, here's an extension that will handle the storage and retrieval of a active user Profile:
extension NSPersistentContainer {
private var managedObjectIDKey: String {
return "ActiveUserObjectID"
}
var activeUser: Profile? {
get {
guard let url = UserDefaults.standard.url(forKey: managedObjectIDKey) else {
return nil
}
guard let managedObjectID = persistentStoreCoordinator.managedObjectID(forURIRepresentation: url) else {
return nil
}
return viewContext.object(with: managedObjectID) as? Profile
}
set {
guard let newValue = newValue else {
UserDefaults.standard.removeObject(forKey: managedObjectIDKey)
return
}
UserDefaults.standard.set(newValue.objectID.uriRepresentation(), forKey: managedObjectIDKey)
}
}
}
This uses a method on NSPersistentStoreCoordinator to construct a NSManagedObjectID from a URI representation.

CKShare "Server record changed" error

I have run into an issue when saving a CKShare. When executing the share everything works as it should, but if the share is cancelled and tried to share again I receive the error:
"Server Record Changed" (14/2004); server message = "client oplock error updating record"
The following is my sharing code:
func share(_ deck: Deck) {
let zoneID = CKManager.defaultManager.sharedZone?.zoneID
if let deckName = deck.name {
selectedDeckForPrivateShare = deckName
}
var records = [CKRecord]()
let deckRecord = deck.deckToCKRecord(zoneID)
records.append(deckRecord)
let reference = CKReference(record: deckRecord, action: .none)
for index in deck.cards {
let cardRecord = index.cardToCKRecord(zoneID)
cardRecord["deck"] = reference
cardRecord.parent = reference
records.append(cardRecord)
}
let share = CKShare(rootRecord: deckRecord)
share[CKShareTitleKey] = "\(String(describing: deck.name)))" as CKRecordValue?
records.append(share)
let sharingController = UICloudSharingController { (controller, preparationCompletion) in
let modifyOP = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: nil)
modifyOP.qualityOfService = .userInteractive
modifyOP.modifyRecordsCompletionBlock = { (savedRecords,deletedRecordIDs,error) in
preparationCompletion(share, CKContainer.default(), error)
controller.delegate = self
if let error = error {
print(error)
}
}
self.privateDatabase.add(modifyOP)
}
sharingController.delegate = self
self.present(sharingController, animated: true, completion: {
})
}
The two methods deckToCKRecord() and cardToCKRecord() are what I use to convert from Core Data to CKRecord so that they can be shared. Thank you.
It's possible that even though the sharing operation is cancelled (since you weren't explicit on what you mean by cancelled), the CKShare record you set up is actually created and saved in your private database. You simply cancelled changing the participation status (e.g. invited) of the other user. Therefore when you go to repeat this procedure, you are trying to re-save the same CKShare, albeit set up again thus likely modifying the change tag. CloudKit sees this and returns a "Server Record Changed" error.
If I'm correct, you could handle this one of two ways. First, set the value of the CKModifyRecordsOperation variable savePolicy to .allKeys, for example, right where you are setting the qualityOfService var. This will mandate that even if CloudKit finds the record on your server, it will automatically overwrite it. If you are sure that the uploaded version should ALWAYS win, this is a good approach (and only results in a single network call).
A second more general approach that most people use is to first make a CKFetchRecordsOperation with the root record's share var recordID. All CKRecord objects have a share var which is a CKReference, but this will be nil if the object hasn't been shared already. If I am right, on your second try after cancelling, you will find the root record's share reference to contain the CKShare you set up on the first try. So grab that share, then with it, initiate the rest of your parent and other references as you did before!

Changing IP address in Realm Object Server

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.

How to set/unset multiple accounts for API credentials

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
}

ACL Role for admin in parse swift

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.

Resources