Healthkit HKObserverQuery Updates with ONLY HKDeletedObject - ios

I have HealthKit background delivery working with a HKObserverQuery and HKAnchoredObjectQuery set up in my AppDelegate as advised in the documentation. While I get callbacks for new data points quickly, I have noticed in my testing that if I make a modification to the Healthkit data that is JUST a deletion (no new HKSample points added), I do not get a callback until the next new point is added. At that point, I will get a callback for the new data point, plus the previous deletes. But I can never get a callback that contains just deletes.
The thing that has me worried is that Apple says that these HKDeletedObjects disappear and won't show up in any queries after some undetermined period of time.
https://developer.apple.com/library/prerelease/ios/documentation/HealthKit/Reference/HKDeletedObject_ClassReference/index.html
So if my app is synchronizing data from HealthKit using this method, what happens in the case where the user deletes some data and then, for a long period of time, doesn't add any new points or launch my app to the foreground? I think that I would just miss the delete entirely in that case.
TLDR: Does the callback for an HKAnchoredObjectQuery ONLY get called in the case where there is at least one new HKSample (even if there are deletes available)? If so, how are we supposed to handle long periods of inactivity after a delete?

To the first part of your question: As I read the documentation of HKStore.enableBackgroundDeliveryForType()... "HealthKit wakes your app whenever new samples of the specified type are saved to the store" - only new samples will trigger the query and this is consistent with my experience.
To the second part, setting HKAnchoredObjectQuery.updateHandler, is causing the anchored query to trigger upon deletes and adds. In my experience though, it is not consistent (see my related question).

Related

CloudKit: Incoming CKNotification's pushed repeatedly

I have my CloudKit setup working pretty well using a private DB with a custom zone. Whenever I fetch I send the latest token I have cached previously, and I trigger a fetch every time I get a CKNotification.
However in one occasion while debugging my app I saw it getting CKNotifications repeatedly, and I could verify that indeed I'd get new tokens and I'd send the latest one on each fetch. As for the data changes themselves, it was always the same record. This particular record needed to be discarded on the receiving device, and so it was, but I don't see that as an issue, I'm saving the change token every time and I acknowledge the notification to be processed with .newData. I'd expect that change to never be notified again!
I tried to recreate the scenario with another record that also needs to be discarded but so far everything seems to work as expected. My concern is that any of my users will ever get to that repeating notifications scenario I once saw going on.
Any ideas of what may have caused it?

What is the difference between an Apple HealthKit query running in the background vs. being enabled for background delivery?

I am reading through the documentation for the Apple HealthKit and am stuck on understanding the difference between a query being registered for background deliveries vs. running on the background. This excerpt from the explanation of anchored object queries seems to differentiate between the two:
Anchored object query. In addition to returning the current snapshot of modified data, an anchored object query can act as a long-running query. If enabled, it continues to run in the background, providing updates as matching samples are added to or removed from the store. Unlike the observer query, these updates include a list of items that have been added or removed; however, anchored object queries cannot be registered for background delivery.
Source: https://developer.apple.com/documentation/healthkit/reading_data_from_healthkit
I am confused about the difference between the two types of "backgrounds." Does the statement "continues to run in the background" really mean "when the app is in the foreground, this query will continue to run without needing to be re-called?"
Background Delivery works like background app refresh, or GPS significant location change events. It wakes up your app to allow you to act on some event. In this case whenever a health sample you are listening for, is added to the store, your app is woken up (once within a given interval) in order to be able to process the event.
Think of a running/cycling app, where a user is trying to tracking their progress on a long workout. The app won't remain open the whole time, but you can ask for the number of steps every X interval so that you can keep your UI updated, or in sync with a server.
More info on background delivery: https://developer.apple.com/documentation/healthkit/hkhealthstore/1614175-enablebackgrounddelivery
The other reference to background, means that it continues to run in another thread, while the app is open. So if you query for heart rate, you can be notified every time a reading comes in, as opposed to just receiving 1 result and having to check every X seconds for a new one

Where and When to get data for Watch Complication

After working with complications for a few days, I feel confident saying the following about the update process for updates that happen at a prescribed interval:
The system calls requestedUpdateDidBegin()
This is where you can determine if your data has changed. If it hasn't, your app doesn't have to do anything. If your data has changed, you need to call either:
reloadTimelineForComplication if all your data needs to be reset.
extendTimelineForComplication if you only need to add new items to the end of the complication timeline.
Note: the system may actually call requestedUpdateBudgetExhausted() instead of requestedUpdateDidBegin() if you've spent too much of your complication's time budget for the day. This is the reason for this question.
If you called reloadTimelineForComplication, the system will call getCurrentTimelineEntryForComplication (along with the future and past variants that get arrays, depending on your time travel settings)
This is conjecture as I haven't tested it yet, but I believe if you called extendTimelineForComplication that only the getTimelineEntriesForComplication(... afterDate date: NSDate ...) would be called.
The system will then call getNextRequestedUpdateDateWithHandler so you can specify how long until your complication requires a new update.
Apple's documentation is quite clear that you should not ask for updates too often, or conduct too much processing in the complication code or you will exhaust your time budget and your complication will stop updating. So, my question is: where and when do you do the update?
For context, my scenario is a URL with return data that changes up to two times per hour.
The most obvious place in which to put the URL fetch code is func requestedUpdateDidBegin() Fetch the data, store it, and if there's no change, just return. If there was a change then extend or reload the timeline.
However, a URL fetch can be costly. Alternatives:
Put the code on the phone app and send it over with a WCSession, but if the user closes that app then the updates will no longer happen.
Use push updates, but this isn't a web app, so I have no place to send them from.
Obviously I will update all the data when the user interacts with the watch app, but that now means it only gets updated when the user uses the app, which negates the need for a complication.
Is there anywhere else? Can I have a periodic function in the watch app that isn't part of the complication? Where is the right place to fetch the data for a complication update?
For watchOS 3, Apple recommends that you switch from using the complication datasource getNextRequestedUpdateDate scheduled update to update your complication.
The old way for watchOS 2
requestedUpdateDidBegin() is really only designed to update the complication. Keeping your complication (and watch app) up to date usually involves far more than reloading the timeline (and asynchronously retrieving data never fit in well with the old approach).
The new way for watchOS 3
The new and better approach is to use background refresh app tasks. You can use a series of background tasks to schedule and handle your app extension being woken in the background to:
Fetch new data
using WKWatchConnectivityRefreshBackgroundTask to obtain data from the phone, or
using WKURLSessionRefreshBackgroundTask to download data from a server
update your model once the data arrives,
update your complication from the model (by reloading or extending the timeline), and finally
update your app's dock snapshot to show the data on the dock
Call each tasks’s setTaskCompleted method as soon as the task is complete.
Other benefits of using app tasks
One of the key features about this design is that the watch extension can now handle a variety of foreground and background scenarios which cover:
initially loading data when your app/complication starts,
updating data in the background, when the extension is woken by a background task, and
updating data in the foreground, when the user resumes your app from the dock.
Apple recommends that you use each opportunity you are given regardless of whether your app is in the foreground or background to keep your complication, app, and dock snapshot up to date.
Are there any limitations?
The number of total available tasks per day is divided among the number of apps in the dock. The fewer apps in the dock, the more tasks your app could utilize. The more apps in the dock, the fewer you can utilize.
If your complication is active, your app can be woken up at least four times an hour.
If your complication is not active, your app is guaranteed to be woken at least once an hour.
Since your app is now running in the background, you're expected to efficiently and quickly complete your background tasks.
Background tasks are limited by the amount of CPU time and CPU usage allowed them. If you exceed the CPU time (or use more than 10% of the CPU while in the background), the system will terminate your app (resulting in a crash).
For more information
A good introduction explaining when and why to update your watch app is covered in Designing Great Apple Watch Experiences.
For specifics, the Keeping Your Watch App Up to Date session covers everything you need to know to keep your complication, app, and dock snapshot up to date.
WatchBackgroundRefresh sample code demonstrates how to use WKRefreshBackgroundTask to update WatchKit apps in the background.
Edit: El Tea (op) has posted a good answer at https://stackoverflow.com/a/32994055/630614
This is an interesting question/problem, and I've been wondering about a lot of the same!
For the most part, it seems that when I'm working on a new complication I need to step back and see when I really want to update it. A "countdown" complication could set all future timeline entries at one time, when the "end date" is set. An app that shows the current status of a web service could have relevant data stored in NSUserDefaults when an APNS comes through.
If you don't have access to APNS, don't want to run your iOS app in a background mode, and don't want to make HTTP requests from Apple Watch, I can think of 2 other options.
1) Schedule local notifications. The good part is that your Apple Watch should run didReceiveLocalNotification, but the bad part is that the user will get a notification when you're simply trying to check the status without a disruption.
2) Send a message to iOS via sendMessage(_:replyHandler:errorHandler:) in your reloadTimelineForComplication method, setting nil for the replyHandler to make it as quick as possible:
Calling this method from your WatchKit extension while it is active and running wakes up the corresponding iOS app in the background and makes it reachable.
Your iOS app could perform whatever network requests are needed and then store the information or push it to Apple Watch. Unfortunately, I don't think the watch extension will have it's session.didReceive... called until you run it, but you could access the data on the next call to requestedUpdateDidBegin.
As I said, I'm very interested in this same thing, so post some thoughts back and maybe we can extrapolate on some best practices here.

Syncing Core Data changes with the apple watch takes too long

I am sharing a UIManagedDocument with my apple watch extension and everything seems to work well. I can query managed objects from my database perfectly. The problem comes when i edit one of those objects. That change is not reflected in the watch app until ~30 seconds later. I repeat the query a number of times, but it's no good until that period passes.
I have tried forcing a save in the managedObjectContext, making double sure i'm running in the main thread, but i just can't get that update immediately.
Is there something else i should do here? or is this a simulator/apple watch limitation?
Thanks!

icloud core data sync

I am starting out with building a iOS app which will utilize core data and will sync its data with other devices through iCloud. I was thinking of giving a "Sync" button so that the data is not modified at random moments by data from iCloud. The following situation would then occur :-
User A is working on device A and adds two records. User B is currently working on device B and does not press the Sync Button. At night, the user B decides to update the data in device B and presses the Sync Button (I have the following questions regarding this situation) :-
Can the method mergeChangesFromContextDidSaveNotification insert those two records after the Sync button is pressed ? (I think it can. However, just want to confirm before proceeding ahead with this model)
Is it possible to access the properties of those two objects before inserting them into the database of device B ?
I do not think that iCloud is designed for your use case. iCloud syncs data across devices for the same user i.e. one that is logged in with the same Apple ID in more than one device. The idea is that when the user opens the app on a different device, he can continue working with the data he created / modified on another device before.
There is quite a bit of latency in the updates, so even if you enable / disable syncing via a "Sync" button the actual delivery time is not guaranteed and can take pretty long in some cases depending on connection speeds, chance, the state of Apple servers, etc.
Instead, you could devise a customised solution based on your own server and data scheme.
Since you're using iCloud with Core Data-- no, you can't do anything like this. When iCloud syncs Core Data changes, it does so in the background. It downloads the changes, saves them to the data store, and then, after it's done it tells you about the change that has already happened. Calling mergeChangesFromContextDidSaveNotification does not save the changes-- they're already saved. What that call does is update your managed object context with new data from the persistent store. You get notified of new insertions after they have already happened. Think of the "did import" notification as if it were a "did save" notification from another thread, telling you about something that's already finished.
Now, if you have already loaded a managed object when you receive the import notification, you could compare its current properties to those from the most recent incoming change update. If you don't already have the object in memory, you can't get its old values, because they've already been overwritten.

Resources