I am trying to store a value at the end of a game.
I am using the https://github.com/matthewpalmer/Locksmith updateData method.
It says on the Github Repo
as well as replacing existing data, this writes data to the keychain if it does not exist already
try Locksmith.updateData(["some key": "another value"], forUserAccount: "myUserAccount")
This is what I have:
let dictionary = Locksmith.loadDataForUserAccount("gameKeyChainValues")
print("Ended \(EndDateAndTimeString)")
do {
try Locksmith.updateData(["BattleEnd": "12345678"], forUserAccount: "gameKeyChainValues")
}
catch {
print("Unable to set time")
}
print("This line ran")
if let timeBattleEnded = dictionary!["BattleEnd"] as? String {
print("Stored for END: \(timeBattleEnded)")
}
This line print("Ended (EndDateAndTimeString)") outputs:
Ended 19:33:38+2016-08-05
This line print("Unable to set time") does nothing
This line print("This line ran") outputs:
This line ran
This line: print("Stored for END: (timeBattleEnded)") does nothing.
When I set a break point and then type po dictionary in the console it shows me other things that are set but not this value.
Can anyone see why?
EDIT:
So, After checking the console. It does appear that directly after I save the information to the Keychain it is there. Then I switch views and update another item in the keychain. This then seems to delete the original one and only keep the new item. Both have different names.
Any ideas?
In the exact words of matthew palmer:
Only one piece of data is stored under an account, so when you call updateData it will overwrite whatever's currently stored for that account
so, Everytime you call Locksmith.updateData it basically clears all the data in there and then adds the new value. You need to send both keys and values together.
Try this:
try Locksmith.updateData(["BattleEnd": "12345678", "Key2": "Value Two"], forUserAccount: "gameKeyChainValues")
The dictionary you fetch in the first line isn't a constantly updating view of the keychain, it's just a copy of what's in the keychain when you call that method. It won't be updated when you update the keychain. If you want the up to date values out of the keychain after you update it, call loadDataForUserAccount() again after you update it.
Related
Assume I have text messages stored in my database and are ordered by childByAutoId() using timestamps. Something like this:
-messages
- dh28hd82jss
...
- nds28h82h1a
...
- di2jd92dhtd
...
What I am trying to do is push() a new message when user crashes.
This is how I typically use onDisconnect operations:
database.child("messages").onDisconnectUpdateChildValues( *update* ){ (error, ref) in
if let error = error {
print("\(error)")
}
completion(true)
}
How can you use push() to append a new text message when a user crashes, using onDisconnect? (text message: "John Disconnected")
You need to know the exact value to write at the moment you call onDisconnectUpdateChildValues. So what you can do is determine a push key now, and then register to write that upon disconnect:
let key = database.childByAutoId().key
database.child("messages").child(key).onDisconnectUpdateChildValues([
"text": "Hello world"
])
I encountered the following error in my swift project: https://github.com/firebase/firebase-ios-sdk/issues/4393. To overcome this issue I change the display name to the characters before the # in a users email like so:
var displayName = user.email!
if let atRange = displayName.range(of: "#") {
displayName.removeSubrange(atRange.lowerBound.. < displayName.endIndex)
}
if user.displayName!.count < 2{
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = displayName
changeRequest?.commitChanges {(err) in
if let err = err{
print(err)
}
}
}
This works when I first log in but if I log out and then back in again the display name reverts back to Optional(""). Why would it be doing this?
Edit
I created a workaround where I run the above code not only when a user is first created, but every time a user logs in, this seems excessive though and there must be a reason why the display name keeps getting overwritten.
This is not an error, this is intended behaviour from Apple's side. By default, you should be updating the Firebase user with the appropriate display name rather than relying on 3rd party information since these are only available on first login.
Simply checking if the name exists on initial login and is longer than 0 characters, not null, etc. then you can update the name with the following:
let changeRequest = Auth.auth().currentUser?.createProfileChangeRequest()
changeRequest?.displayName = displayName
changeRequest?.commitChanges { (error) in
// ...
}
This is also documented within the git-issue you posted https://github.com/firebase/firebase-ios-sdk/issues/4393#issuecomment-612193703
Ultimately, you are logging into your app as a Firebase user, not an apple user. Apple is only Authenticating the process, so you must update the Firebase user where possible as Apple is highly inconsistent across all platforms.
I am trying to update realm list but for some reason, it deletes last one and adds only new one instead.
This is what I am doing:
if !RealmService.shared.ifPortfolioExists(name: newPortfolio.name){//Check if portfolio with given name already exists
newPortfolio.transactions.append(newTransaction)//Add new transaction to the portfolio's transactions list
newTransaction.portfolio = newPortfolio//Link portfolio to transaction
RealmService.shared.create(newPortfolio)
RealmService.shared.create(newTransaction)
}else{
newPortfolio.transactions.append(newTransaction)
newTransaction.portfolio = newPortfolio
RealmService.shared.create(newTransaction)
}
And this is the create function:
func create<T: Object>(_ object: T){
do {
try realm.write{
realm.add(object, update: true)
}
} catch {
post(error)
}
}
I also have primaryKeys() like mentioned in documentation.
What I am doing wrong, can somebody please explain?
Per the Realm class description, the object has to have a primary key for the update to work. Since you didn’t mention that, I assume your realm object lacks it.
Only pass true to update if the object has a primary key. If no object exists in the Realm with the same primary key value, the object is inserted. Otherwise, the existing object is updated with any changed values.
Solution was that I had to get all the current ones first.
This is the logic I applied:
let currentTransactions = RealmService.shared.realm.objects(Transaction.self).filter("portfolio_name = '\(selectedPortfolioName)'")
if !RealmService.shared.ifPortfolioExists(name: newPortfolio.name){
for transaction in currentTransactions{
newPortfolio.transactions.append(transaction)
}
newPortfolio.transactions.append(newTransaction)
newTransaction.portfolio = newPortfolio
RealmService.shared.create(newPortfolio)
RealmService.shared.create(newTransaction)
}else{
for transaction in currentTransactions{
newPortfolio.transactions.append(transaction)
}
newPortfolio.transactions.append(newTransaction)
newTransaction.portfolio = newPortfolio
RealmService.shared.create(newTransaction)
}
I came across a strange behaviour. Even though my unit tests are passing, I don;t quite understand this.
func testDeleteMoment() {
// Create a Moment for current user
let expectCreate = expectation(description: "create moment should succeed")
Datastore.shared.createMoment(notes: "myNotes", rating: 1, time: Date(), location: "", isPublic: true, completion: {(success, error) in
XCTAssertTrue(success)
expectCreate.fulfill()
})
waitForExpectations(timeout: 5) { (error) in
XCTAssertNil(error, "Test timed out. \(String(describing: error?.localizedDescription))")
}
let query = PFQuery(className: "Moment")
var objectId = ""
do {
query.whereKey("owner", equalTo:PFUser.current()!)
let object = try query.getFirstObject()
objectId = object.objectId!
} catch {}
let task = Datastore.shared.deleteMoment(id: objectId)
task.waitUntilFinished()
let query2 = PFQuery(className: "Moment")
query2.whereKey("owner", equalTo:PFUser.current()!)
let task3 = query2.countObjectsInBackground()
task3.waitUntilFinished()
XCTAssertEqual(task3.result, 0)
}
While writing my datastore.deleteMoment(), I noticed that unlike saveEventually(), deleteEventually() doesn't have a completion handler. Instead, it only comes with a BFTask<NSNumber>. Hence I experimented with the following code and the unit test passes to my surprise.
func deleteMoment(id: String) -> BFTask<NSNumber> {
let pfMoment = PFObject(className:"Moment")
pfMoment.objectId = id
return pfMoment.deleteEventually()
}
How comes that I don't have to retrieve the object before hand in order to delete it? Why isn't there then just a method to delete the object via an id, instead of doing it like this?
How comes that I don't have to retrieve the object before hand in order to delete it?
Parse lets you work with "shell objects". Basically, if you know the object id you can attach it to a blank instance of a Parse Object subclass and do some actions on it. You can create a shell, assign a couple values, and call object.save() and it will update just those fields. You can create a blank object, assign an id, then call object.fetch() to obtain it, without having to have gotten the object from a query or pointer on another object.
The reason it works is because in order to perform the database operations, the object id and class name are the only pieces of information required. If you just want to update a couple fields, you don't need to pull all of the rest of the data to do so. And if you want to destroy an object, why would you need all of it's data locally first? You just need to remove the entry in the database with the matching _id.
Why isn't there then just a method to delete the object via an id, instead of doing it like this?
Nobody has built it yet. Parse is open source, and while it's a pretty phenomenal resource today, there's certainly a lot of room for improvement. Feel free to add this feature and create a PR. Though you could easily build your own wrapper by extending the Parse.Object class with this function, that basically does what you already did under the hood.
I'm building an app and I need a connection with a database. I'm using Parse for this (and hosting it with back4app if that does matter).
I have already a lot of experience with Parse so it's very weird that I have this problem.
So I need to update an object but it doesn't. Could somebody please help me out? I've putted in my code an print and it does print so you know the app get's there but strangely enough, it doesn't update the object.
Thanks in advance!
EDIT: the code works because when I run the app in the simulator it updates but when I run it on my iPhone, it doens't?
func makeMessagesSeen() {
let query = PFQuery(className: "Chat")
query.whereKey("senderID", equalTo: otherID)
query.whereKey("receiverID", equalTo: yourID)
query.order(byDescending: "createdAt")
var objects = try! query.findObjects()
for i in objects {
if (objects[0].objectId == i.objectId) {
print("this will print , idk why code beneath this doesn't work?")
i["messageSeen"] = "last seen"
i.saveInBackground()
}
else {
i["messageSeen"] = "seen"
i.saveInBackground()
}
}
}
I fixed it and will post the solution here in case anyone will make the same mistake.
So I created the message object with my simulator and parse automatically set the write acces for everyone to false. Only for the device that created the message it was set to true. So that's why I could only update to object with my simulator.
So change in your database the write acces for everyone to true.