Alternatives to UPDATE notification that does not work - ios

UPDATE notification stopped working. What workaround do you have, until Apple figures out something?
CKFetchNotificationChangesOperation sometimes does not return UPDATE, DELETE notifications
CloudKit push notifications on record update stopped working
https://forums.developer.apple.com/thread/7288
My quick fix, that in every minute, and when user triggers UIRefreshControl, then I downloading all the records that were modified since the last update. It works. But I have a better idea in my mind, curious, what do you think. Any drawbacks.
I am thinking adding a Change table to the database. It will have a recordName, recordType, changeType fields. changeType could have insert, update, delete string values. (or int enumerations accordingly)
And I would create and observe with CKSubscription only the Change table. Always when a record is create, updated, deleted in a custom table, I would do two things, do that action and as a second action I would make a 'log entry' into the Change table. Because it is an Insert operation in Change, and insert notification seems to work, all the device would get notified. They could download the refered record.
Do you see any drawbacks?

I have used a similar strategy. There are some drawbacks:
more data (storage and transfer) usage
limited subscription possibilities or you have to duplicate even more data.
extra code in your app that you actually don't want to be there.
The only good solution to this problem is:
Apple should fix it...

Related

Overview of how to track mail move change notification ms graph api

Our platform is replicating email functionality, I.e. view all emails folders and contents, reply, create new, draft, move etc for users.
We have successfully subscribed users to all change notifications (create, update, & delete) for the whole mailbox, however, we are not sure how to track folder move operations as I can’t find an example in the documentation and our current implementation is not working reliably.
The issue we have is that after receiving the various change notifications, when we are doing the requests to get the value for the updated/deleted message, sometimes the value returned is the updated value, not the original one, therefore if the folder has changed we do not know which message to delete. This issue is highlighted in the documentation here (half way down): https://learn.microsoft.com/en-us/graph/outlook-change-notifications-overview?tabs=http#example-3-create-a-subscription-to-get-change-notifications-with-resource-data-for-a-message-based-on-a-condition-preview
We tried it using immutable ids, but the final webhook received was sometimes the delete webhook for the message ID. This is supposed to be for the original email that was moved, however because the message ID is the same (and the parent folder ID value is not reliable) we may end up deleting the wrong email.
With immutable turned off, we did not receive any delete notifications (only creates and updates), so we ended up with duplicate emails as the original was never deleted.
Is someone able to advise the correct procedure to track these events?
Thanks
So it turns out the only reliable way to do this is using the delta query - https://learn.microsoft.com/en-us/graph/delta-query-messages - whenever a change notification is received for a folder.
So, when authorisation is provided for access to a users mailbox, you must get subscriptions for each folder and then whenever a change notification is received for that folder/subscription, the delta query is run for that folder.
I believe that MS are in beta testing for providing the change information in the webhook which in my option would be a great improvement in efficiency in terms of implementation and operation.
Hope this helps someone in the future!

How do I prevent orphans when deleting a record from CloudKit?

The CloudKit WWDC videos recommend implementing sync like this:
Track local changes
Send changes to the server
Resolve conflicts
Fetch server changes with CKFetchRecordChangesOperation
Apply server changes
Save server change token
I'm following this pattern in my app, but I'm running into a problem with deletion and parent-child relationships.
Let's say we have a list of books that are split up into categories. Every book has to belong to exactly one category.
I start with data like this:
SERVER
Thrillers: "Look Out!", "Secret Spy"
Non-Fiction: "Sailing the Seas", "Gardening Adventures"
Computer Programming: <empty>
As you can see, the final category is empty. Let's say I have two devices with exact copies of this data.
Now, on Device 1, the user adds a book CloudKit Sync to "Computer Programming":
DEVICE 1
Thrillers: "Look Out!", "Secret Spy"
Non-Fiction: "Sailing the Seas", "Gardening Adventures"
Computer Programming: "CloudKit Sync"
But on Device 2, the user completely deletes the "Computer Programming" category (it's empty, so this is fine from Device 2's point-of-view):
DEVICE 2
Thrillers: "Look Out!", "Secret Spy"
Non-Fiction: "Sailing the Seas", "Gardening Adventures"
Device 1 syncs first, so it creates a new Book entry with its parent field set to Computer Programming.
But now Device 2 starts its sync process. It applies its changes to the server, so it deletes the CKRecord corresponding to "Computer Programming". This is consistent with Device 2's worldview, where the category is empty and can be deleted.
However, when it deletes this category from the server, this doesn't make sense with respect to the worldview of Device 1 and the server itself. There's now an orphan book called CloudKit Sync that has a dangling pointer to its parent.
If I'm following Apple's recommendations from WWDC, how do I avoid this scenario? Depending on the order of the sync, I can easily arrive at an inconsistent state with an orphaned book and an invalid parent reference.
What I'd like to happen is for the Delete command from Device 2 to return an error telling me I'm going to orphan a book and prevent the action from occurring at all, so I can take some action to fix the situation.
Is that possible? Is there another way to approach this?
Yes, the behavior you want for Device 2 is possible. I see three aspects of cloudkit that will come into play in your scenario. Let's look at those first, then how they might be used in your scenario.
First, assuming that both (or all) devices have subscribed to changes to the appropriate records, each device would be notified that someone else added or removed something. The device receiving the alert would then have the opportunity to decide what to do about it. (remove it from it's local view, replace it on the server, etc)
Second, you can set the behavior for handling conflicts using the savePolicy on the CKModifyRecordOperation. You can specify whether the last change should overwrite older records, throw an error, etc. See https://developer.apple.com/documentation/cloudkit/ckrecordsavepolicy?language=objc for the three options. (I've only used this in the context of two users modifying a common record, but a deletion after another user updated the record should then throw a server record changed error).
Third, assuming you've configured the aforementioned savePolicy, is the server change token itself. I find it easiest to envision the change token as just a last-modified timestamp. "My copy of this record was last modified at 10:42pm" kind of thing. Depending on the overwrite options you've selected in the aforementioned savePolicy, the device will receive an NSError Server Record Changed alerting you that the version on the server is from, say, 10:56pm, and that your local version may no longer be valid.
The userInfo in the resulting NSError includes 3 versions of the record in question: the current version on the server, the version you tried to submit, and the common ancestor version. The guides from Apple say it's up to the developer to decide what how to merge this information. But in theory, you'd be able to diff the changes, decide which you want to keep, and then submit a new operation.
Regarding your specific scenario: Assuming you fully authorize and trust both dev1 and dev2 to delete records, then I would subscribe to creation and deletion events, and set the savePolicy to throw an error when attempting a conflicting change. In this case, Device 1 would add the record and Device 2 would receive the notification of the new record. If Device 2 simply attempts to delete the old record, it should fail with a server record changed error, which you could display to the user as
"Someone else modified this record, do you really want to delete it
(y/n)."
Device 2 would have to refresh the record (and receive the new record change token) before proceeding. After that, if Device 2 still wants to delete the new record, it could, but then Device 1 would be notified of the change via the aforementioned subscription. Device 1 would then download the new record to (or in this case remove the old record from) its local view. The subscription notification could alert user 1:
"Your record Foo was just deleted by Bar"
This will work even if the events happen practically simultaneously, because one of the changes will be applied on the server first and the other device's token will immediately become out-of-date. So, if Device 2 managed to delete the record first, Device 1's attempt to modify the record will fail with server record changed because Device 1's change token is now out of date. Device 1's error handler would have to decide whether to honor the deletion or to proceed with creating a new record based on your business rules. Maybe ask user 1 with something like:
"Computer Programming" has been removed from the server. Do you want to recreate
it?
At this point, user1 can send flame emails demanding other users stop deleting their newly created records, and user2 can demand that people stop recreating the records they just "cleaned up." :)
You could get a lot more complicated, maybe giving device 1 precedence over device 2, such that when device 1 is notified that the record is deleted, then device 1 re-writes the record to the server. If you have multiple users with deletion rights, you could determine an order of precedence and build out the appropriate error/notification handlers. However, this seems excruciating complicated and error prone. Loops that auto respond (create, delete, create, delete, create, delete) could occur. I include it only as a hypothetical example, not a recommendation!
Lastly, as a different example, my app has a different scenario. The records in my case are gaming sessions. All players need read access to the session data, but only the originator is given the option to delete the record altogether. So, you might consider whether you really authorize multiple users to delete shared records or not.

CloudKit Delete Self option for CKReference Doesn't work

Does anybody have experience using the CloudKit option to DeleteSelf for a CKReference? Here is what I got from the docs:
DeleteSelf
The delete action for referenced records. Deleting a record also deletes any records containing CKReference objects that point to that record. The deletion of the additional records may trigger a cascade deletion of more records. The deletions are asynchronous in the default zone and immediate in a custom zone.
So I've been testing this out, I have created multiple CKReference objects both using the CloudKit Dashboard and by using my app. Whenever I do it with my app I create the reference like so:
let reference:CKReference = CKReference(recordID: savedFriend.friendID, action: CKReferenceAction.DeleteSelf)
I know that it is being assigned to delete itself, because in the dashboard it shows the option checked off:
I must be doing something wrong because whenever I go and manually delete that record that is referenced using the dashboard, or set my app to delete it programmatically, the record disappears but the reference never gets removed. I thought maybe I should just wait and it would happen eventually, but hours later it's still there checked off like it should have deleted itself. It's kind of frustrating because I designed some of my code assuming this would work, now I can have an app that's wasting resources trying to find a CKRecord that doesn't exist anymore. Should I just not rely on this to ever work? I appreciate any help or advice I can get.
UPDATE ~ 04/07/2016
I jumped the gun coming back here and posting an update that the issue had resolved itself. I wasn't totally convinced that the issue wouldn't pop back up so I sought some reassurance last night. I created 5 different CKRecords and created various different CKReference objects to reference them throughout my database. I then proceeded to set the CKReferenceAction.DeleteSelf option on each reference (I tried two manually through the dashboard and the other three done programmatically). Anyways, I waited a few minutes and then deleted the referenced records...after ten minutes the references were all still present. I waited another 5-10 minutes and they were still there so I went to bed assuming they should definitely be gone by morning, right? Wrong! Twelve hours later the CKReference entries are still there and the referenced records are still gone. I'm definitely scratching my head, it seems as though it's a bug with CloudKit. I should mention that I've also noticed some weird behavior in my dashboard. For the past four or so days, down in the bottom left hand corner it has consistently said it's "Reindexing development data", here is a picture for example:
Could that be causing this issue? Is anybody familiar with this issue and knows a way to solve this? Any suggestions would be appreciated. I have filed a bug report with Apple.
This is most likely a permission issue.
The cascading delete will only work if the user deleting the records has 'write' permissions to all the records needing to be deleted. So in the CloudKit Dashboard, the cascading delete will only work for the records created with the developer's iCloud account.
See my answer there: https://stackoverflow.com/a/38606004/6643055

Creating CloudKit records on scheduled days

I'm working on an app right now that lets the user create new records on a recurring basis, and lets the schedule how many days in advance they want to create those records. This will almost always take place at a time when the app is not running, so I'm exploring my options for getting the app to be woken up to create the objects. As far as I see it there are four options, one of which is probably not really an option, since I'm trying to use only CloudKit and avoid creating a server-side component.
Option 1:
Use Background fetch to periodically refresh the records and check to see if anything is changed. In this instance I would probably have some kind of CloudKit record that represented the recurrence, in addition to the record type that needs to be created. Then I can just check the recurrence object and create a new record if needed
Option 2:
Schedule a local notification when the user schedules the recurrence. I thought this was going to work, but as far as I can tell, the app will not actually be launched even if it's a silent notification, unlike remote notifications.
Option 3:
Write some code locally that will check a data structure whenever the app is launched to check to see if any new records need to be created.
Option 4:
Create a server application that will create the records for me. Like I said, not really something that I want to do, even if it's the "best" option usually.
With option 1 you can not be certain that your app is running.
Option 2 won't work. As you said the app won't be activated. Or the notification must nog be silent and the user has to select to open the app.
Option 3 is much better than 1 and 2 if you don't mind that it could happen that the records won't be created for a long time if the user never opens up your app.
Option 4 is the best option for this. you will have full control over when to add what.
But... Do you already know in front what data to add? Then why are you adding that data? Can't you just calculate it and assume it's there? Maybe even based on a settings record or so?

Allow users to remove their account

I am developing a gallery which allows users to post photos, comments, vote and do many other tasks.
Now I think that it is correct to allow users to unsubscribe and remove all their data if they want to. However it is difficult to allow such a thing because you run the risk to break your application (e.g. what should I do when a comment has many replies? what should I do with pages that have many revisions by different users?).
Photos can be easily removed, but for other data (i.e. comments, revisions...) I thought that there are three possibilities:
assign it to the admin
assign it to a user called "removed-user"
mantain the current associations (i.e. the user ID) and only rename user's data (e.g. assign a new username such as "removed-user-24" and a non-existent e-mail such as "noreply-removed-user-24#mysite.com"
What are the best practices to follow when we allow users to remove their accounts? How do you implement them (particularly in Rails)?
I've typically solved this type of problem by having an active flag on user, and simply setting active to false when the user is deleted. That way I maintain referential integrity throughout the system even if a user is "deleted". In the business layer I always validate a user is active before allowing them to perform operations. I also filter inactive users when retrieving data.
The usual thing to do is instead of deleting them from a database, add a boolean flag field and have it be true for valid users and false for invalid users. You will have to add code to filter on the flag. You should also remove all relevant data from the user that you can. The primary purpose of this flag is to keep the links intact. It is a variant of the renaming the user's data, but the flag will be easier to check.
Ideally in a system you would not want to "hard delete" data. The best way I know of and that we have implemented in past is "soft delete". Maintain a status column in all your data tables which ideally refers to the fact whether the row is active or not. Any row when created is "Active" by default; however as entries are deleted; they are made inactive.
All select queries which display data on screen filter results for only "active records". This way you get following advantages:
1. Data Recovery is possible.
2. You can have a scheduled task on database level, which can take care of hard deletes of once in a way; if really needed. (Like a SQL procedure or something)
3. You can have an admin screen to be able to decide which accounts, entries etc you'd really want to mark for deletion
4. A temperory disabling of account can also be implemented with same solution.
In prod environments where I have worked on, a hard delete is a strict No-No. Infact audits are maintained for deletes also. But if application is really small; it'd be upto user.
I would still suggest a "virtual delete" or a "soft delete" with periodic cleanup on db level; which will be faster efficient and optimized way of cleaning up.
I generally don't like to delete anything and instead opt to mark records as deleted/unpublished using states (with AASM i.e. acts as state machine).
I prefer states and events to just using flags as you can use events to update attributes and send emails etc. in one foul swoop. Then check states to decide what to do later on.
HTH.
I would recommend putting in a delete date field that contains the date/time the user unsubscribed - not only to the user record, but to all information related to that user. The app should check the field prior to displaying anything. You can then run a hard delete for all records 30 days (your choice of time) after the delete date. This will allow the information not to be shown (you will probably need to update the app in a few places), time to allow the user to re-subscribe (accidental or rethinking) and a scheduled process to delete old data. I would remove ALL information about the member and any related comments about the member or their prior published data (photos, etc.)
I am sure it changing lot since update with Data Protection and GDPR, etc.
the reason I found this page as I was looking for advice because of new Apply policy on account deletion requirements extended https://developer.apple.com/news/?id=i71db0mv
We are using Ruby on Rails right now. Your answers seem a little outdated? or not or still useful right now
I was thinking something like that
create a new table “old_user_table” with old user_id , First name, Second name, email, and booking slug.
It will allow keep all users who did previous booking. And deleted their user ID in the app. We need to keep all records for booking for audit purpose in the last 5 years in the app.
the user setup with this app, the user but never booking, then the user will not transfer to “old_user_table” cos the user never booking.
Does it make sense? something like that?

Resources