While starting to work more with CloudKit I just realized that I don't actually know what a RecordID is...
I'm looking at the CloudKit dashboard right now, I see RecordTypes, RecordName etc, but I don't see RecordID. The iOS Dev library mentions RecordID a lot, but never actually tells what it is or where to find it.
I guess I'm just dumb, but I can't figure it out.
Every record has a record Id, which is a CKRecordID instance, and the class has a name property. If you don't specify a name, new records will have a record Id with a name that is a GUID.
You can only fetch with record Id if you know it, and in most instances you will let Cloud Kit create on for you, and you won't store it locally, so you won't know it.
Every CKRecord has a bunch of metadata, which includes the record id. See the list here.
Here a snapshot of the dashboard, your recognised it. You can either accept the recordIDs that CloudKit gives you or generate your own [although they must be unique within your database instance]. Here the screenshot
And here a small code snippet to show you how to create your own ID, using the same method I suspect CloudKit uses.
let uniqueReference = NSUUID().UUIDString
let uniqueRecordID = CKRecordID(recordName: uniqReference)
let newRecord = CKRecord(recordType: "Collection", recordID:uniqueRecordID)
Related
I am trying to update one of the parents in my database. However, I am unsuccessful. Here is what the DB looks like:
I am able to Update the description and due date like so:
taskRef = Database.database().reference(withPath: "Tasks").child(titleOfTask)
taskRef?.updateChildValues(["Due Date": date_time])
taskRef?.updateChildValues(["Description": taskDescription])
However, I am trying to figure out how to update the name of the task. For example, if the user is trying to update the description of task "Alpha" and also decides to rename the task as well, how can I update the name?
I first tried to do this, but it didn't work, it just created and another key, value pair under the "Alpha" parent.
newTaskRef?.updateChildValues([titleOfTask: taskTitle])
Then I realized that my database reference is to the Task Title already, so it wouldn't have worked. Then I thought about creating another database reference to just "Tasks", and updating the title like so:
newTaskRef = Database.database().reference(withPath: "Tasks")
newTaskRef?.updateChildValues([titleOfTask: taskTitle])
But this didn't work either. Not sure what else I could try, or where I am going wrong.
Important Things:
taskTitle holds the new input the user enters
titleOfTask is the old name of the task. (i.e, used as a reference to read from DB)
in other words:
If the user wants to edit the "Alpha" Task, we would have to pass the STRING "Alpha" to the database.reference so we can read its values and update the description and/or due date. So the original Title of Task is stored in "titleOfTask", and the new task title would be stored in "taskTitle"
You can't directly change the keys in the DB. Instead you can follow a 2 step approach:
Create a new key with the updated title and info.
Delete the old key from the DB
I am struggling to overcome to issue I have. I am trying to delete a parent node/key given I find the correct child value.
My database is structure liked this
I am querying my database by a certain value, objectID, as it will match the postID which is passed through the parameters. The objectID value is removed. However, I am struggling to remove the key in which it falls under.
I have had mixed results so far:
I can either remove the objectID value using this code:
refSnap?.ref.child("objectID").child(postID).removeValue()
Of I can remove the whole notifications node/directory, using this:
refSnap?.ref.child("objectID").queryEqual(toValue: postID).ref.removeValue()
refSnap?.key gives me all the keys/nodes under the notifications node.
I cannot access the key which the objectID and all other information is stored under as it is .childByAutoId. Can anyone possibly help me as to how I can sort this issue?
While the other answer provides some insight with an alternate and workable structure, the ability to delete a node based on a child is fairly straightforward and directly addresses the question without changing the structure.
(note that the structure may need to be changed anyway but for this exercise, we'll use it as is.)
Given a structure suggested in the question:
notification
"rtupy..." //childByAutoId
"-LFEMjAcny..." // childByAutoId
"-LFEzrrq..." // childByAutoId
from: "aw,sdasdad"
objectID: "-LFEMjAcn...."
timestamp: 15292
type: "comment"
Suppose you want to delete the node "-LFEzrrq..." as shown in your screenshot. That node contains the child objectID: "-LFEMjAcn...."
To delete the node you need query for the node that contains the objectID you want which, according to the question, is working and returning the correct child.
Use the returned snapshot to obtain it's parent key, and get the path to that node and delete it. Note that we don't know what process or code the OP used to obtain the node they want to delete - perhaps it was from another query and the node reference was passed in or some other means.
let queryRef = //unknown how, but build the query for objectID = "-LFEMjAcny...."
queryRef.observeSingleEvent(of: .value, with: { snapshot in
let key = snapshot.key //this is the parent key of the objectID node i.e. -LFEMzrrq..."
let parentRef = snapshot.ref.parent! //this is the path to that parent
let refToDelete = parentRef.child(key) //add the parent key to the path: -LFEzrrq
refToDelete.removeValue() //delete it
})
As you can see, regardless of the parent nodes' key, or how deep it is, this code will delete the node found in the query.
The key names do not matter so using .childByAutoId as references to tie your nodes together is safe and generally best practice as disassociating node keys from the data they contain makes your structure highly expandable.
Your issue is that you are using childByAutoId and you can't "tie" these random alphanumerics to something(in this case the post).
The structure that i would set is this:
-notifications
--uid //notifications for user
--- userAid+userBid //follow notification, if they unfollow you already now which one it is and you can go and delete it
---commentNotificationID // you give this notification the same Id that the comment has, so if the user deletes the comment you use that id to delete the notification as well.
I am trying to make a money related app which shows the user their spendings as a project to get used to Firebase. So I stumbled upon this issue; I can't seem to figure out how to add more than one expense assigned to a user. Whenever I add a new expense, the existing value in Firebase gets reset to the new value but I want it to store both the new and the old value. How can I do this?
There's something called "autoID" if that helps. I'll link that in a few moments.
Edit: It's used here.
childByAutoId() is what you want for swift. See Updating Or Deleting specific data section in the Read and Write Data on iOS
let thisUserRef = ref.child("users").child(users uid)
let expenseRef = thisUserRef.child("expenses").childByAutoId()
let dict = ["expense_type": "travel", "expense_amt": "1.99"]
expenseRef.setValue(dict)
will results in
users
uid_0
-Y88jn90skda //<- the node key created with childByAutoId
expense_type: "travel"
expense_amt: "1.99"
the childByAutoId is super powerful and allows 'random' node keys to be generated that contain your child data.
See the answer to this question and this other question for some tips on data modeling and queries.
Given that an ABRecordID can change between cloud syncs and under other circumstances out of my control, how can I maintain a long-term reference to an IOS address book record?
Apple provides the following guidance:
"The recommended way to keep a long-term reference to a particular record is to store the first and last name, or a hash of the first and last name, in addition to the identifier. When you look up a record by ID, compare the record’s name to your stored name. If they don’t match, use the stored name to find the record, and store the new ID for the record."
But I don't understand this guidance. If the address book can have duplicate names in it AND since users can modify the name in a record how could this advice work?
For example, if the user modifies the name of an address book record my routine will fail to find it by ABRecordID so if I think search by the name hash I stored couldn't I find a duplicate name instead of the new ABRecordID for that specific record I previously referenced?
In the end, what is the BEST way to get a long-term reference to an IOS AddressBook record? And if the above advice really does work what am I missing?
The most robust (yet not completely failsafe) approach would be to come up with a priority ranking of ABRecord fields and store as much from that list as is available, along with the ABRecordID, into your own (hashed) private record format. When retrieving a private record (or at another convenient time), you can verify that the private record matches the ABRecord and work through a series of fallback checks to ensure it's accurate.
Example priority ranking:
ABRecordID
FirstName
LastName
PhoneNumber
ZipCode
When retrieving a record you can first match the ABRecordID. If that returns no results, you can do a search for FirstName + LastName. You can then match those results against PhoneNumber... etc. In this way you could potentially distinguish between 2 Bob Smiths, as they may have different phone numbers (or one may not have a phone number). Of course, depending on how long your priority list is, the more robust this mechanism will be.
The last resort would be prompting the user to distinguish between 2 Bob Smiths with brand new ABRecordID's whose records are otherwise identical -- after all, such an inconvenient prompt would be far more friendly than allowing the User to contact the wrong Bob Smith (and as I said, would be a last resort).
This solution for AB may involve some synchronization issues, however.
This is a familiar problem for anyone who has worked with the iOS Media Player. Specifically MPMediaItems in the User's Music Library have a property MPMediaItemPropertyPersistentID which the docs describe as:
The value is not guaranteed to persist across a sync/unsync/sync cycle.
In other words, the PersistentID is not guaranteed to be persistent. Solutions for this include doing similar fallback checks on MediaItem properties.
The RecordID only get changed either on delete or reset, when this is done all the new record(s) will have new createdProperty and modifiedProperty as well.
While I am reading the address book for the first time, I will save all entries of the record along with RecordID in my database.
I will save the last time the contacts synced from contacts to my database(name it something: lastSyncedTime) and store it some where.
I am done with syncing the contacts for the first time, now do the following for syncing anytime in future.
while Iterating through all records,
check createdTime(kABPersonCreationDateProperty) vs lastSyncedTime. If createdTime > lastSyncedTime, store the recordID in a "newRecords" NSArray.
If !(step 1) then check modifiedDate(kABPersonModificationDateProperty) vs lastSyncedTime. If modifiedDate > lastSyncedTime, then store the recordID in a "modifiedRecords" NSArray.
if !(1) && !(2) store all recordID in a "unModifiedRecords".
Now I will read all the contacts from my local database,
I will delete all local database records that are not find either in "modifiedRecords" or in "unModifiedRecords".
I will update all "modifiedRecords" in the local database.
I will create new records for all records in "newRecords".
Update the lastSyncedTime accordingly.
The documentation is communicating to you that you can't count on ABRecordID as a permanent identifier.
Consider this scenario: The user has a record for "Bob Smith". The user then deletes his "Bob Smith" record and then imports his contacts from his computer (creating a new ID) through iTunes sync.
So if you want to keep a permanent reference to an existing contact, you can keep a reference to the name and id as a hint that it is the same record you used before- but there is no real permanent reference.
If you keep a permanent reference to an address book contact, you must always be ready to deal with the fact that it may not be the same contact you used before.
Refer :
https://developer.apple.com/library/ios/documentation/ContactData/Conceptual/AddressBookProgrammingGuideforiPhone/Chapters/DirectInteraction.html#//apple_ref/doc/uid/TP40007744-CH6-SW2
Clearly tells you how to handle it.
In many of my apps, it requires associating some data with a contact in addressbook. What I used to do is save the record id of an ABPerson and use that id to pull information upon each app launch. However, more and more I find that this approach is wrong because many times a user will use a service like mobileme where the addressbook is wiped and resynced. This causes the record id to change and all associations are lost. The user will have to go through each one and re-link them.
What is a better approach to holding a robust pointer to addressbook entries?
You should store three values: the record ID, the first name, and the last name.
1) In the case that the record ID hasn't changed, you're golden - just use that to locate the proper record.
2) If ABAddressBookGetPersonWithRecordID() does not locate a record for your stored record ID (it returns NULL), then you'll need to search the person records for a match based on the first and last name. You can drop down to using ABAddressBookCopyPeopleWithName() potentially or write your own locating code if you already have an array with all the person records in-memory. Locating the new record is up to you. Once to locate the new record, you can update your data storage with the new record ID.
Ultimately, you end up storing the record ID to use directly incase it doesn't change (if you're lucky) plus storing some keys from the address book entry that are unlikely to change. The name of the person or organization associated with an address book entry is most likely to change. You should, of course, account for the case where you may not find a record with the stored record ID or by searching for the name. This could trivially mean that the record was deleted, or it could mean that the record was renamed. You should handle that case whichever way you decide is best for your specific application.
I know this was last year, however, I thought I might suggest a method I use. The first time I ask the user to pick a contact (in order to associate certain of my app's private data with it) I then grab the record, create my own internal record id (the initials of the app name and a sequence number usually) modify the contact by adding a new ABRelatedName (type of "pref" name of "Other") value of my own internal record id. It looks like this in the .vcf
item3.X-ABRELATEDNAMES;type=pref:BZA101
item3.X-ABLabel:_$!<Other>!$_
That way, I can simply reference that record id when i add more data about the user such as the last time the app user contacted them, etc. Seems to work for me.
Hope that helps someone.
If the address book is indeed being completely wiped and re-loaded, and the only part that doesn't change is the display name, then storing the display name as the link seems like the only option.