Maximise background update on WatchOS

I'm looking to maximise my Watch app's widget to be as up to date as possible.

If we imagined the app was a simple step counter, and we wanted to display the users count as up to date as possible. We can conclude:

  • We don't care about widget timelines beyond the current entry as we can't predict the future!
  • We need to refresh the count as often as possible
  • The refresh should be very quick with a straightforward HealthKit query, no networking or heavy work needed.
  • We will assume the user has the complication/widget on their active Watch face.

With the standard WidgetKit APIs we can expire the timeline after 15 minutes and in my experimentation a Watch app can usually update its widget timeline at that frequency if it's on the Watch face.

I'm experimenting with two methods to try and improve refreshes further

  1. A user's step count might not have recently changed when the timeline update is called. I was therefore looking into the HealthKit enableBackgroundDelivery API (which requires the HealthKit Background Delivery entitlement to be enabled) to get updates limited to once an hour from a HKObserverQuery, I can then call the WidgetCenter.shared.reloadAllTimelines() from there.
  2. WatchOS also support the BGAppRefreshTaskRequest(identifier:"") and .backgroundTask(.appRefresh) APIs. I can request updates once every 15 minutes here too and then call the WidgetCenter.shared.reloadAllTimelines().

With option 1, this update opportunity is great as it will specifically update when there's new steps so even once an hour this would be helpful (A real shame to be limited to once an hour even if this used up WidgetKit standard reload budgets: FB13879817, FB11677132, FB10016177). But I can't determine if this update takes away one of the standard timeline expiration updates that already run 4 times an hour? Could I observe additional Health types to get additional updates? Do I need the Background Modes Capability as well as the HealthKit Background Delivery for this in Xcode or just the HealthKit one?

With option 2, I can't find a suitable option in the (short) list of supported background modes in Xcode. Does not selecting any mean my app will get 0 refreshes from this route and so should not be implemented in my use case?

Answered by DTS Engineer in 844882022

With option 1, this update opportunity is great as it will specifically update when there's new steps so even once an hour this would be helpful

Yeah, I'd say option 1 is the right way to go in your case.

But I can't determine if this update takes away one of the standard timeline expiration updates that already run 4 times an hour?

If reloadAllTimelines is called when your watchOS app is running in the foreground, the reload won't be counted against the budget; otherwise, it will. This is discussed in Keeping a widget up to date.

Could I observe additional Health types to get additional updates?

Yes, though an additional data type can change at a different pace, and hence your app can be woken up more frequently. This is somehow discussed in this post.

Do I need the Background Modes Capability as well as the HealthKit Background Delivery for this in Xcode or just the HealthKit one?

I don't think you need any background mode other than HealthKit Background Delivery.

WatchOS also support the BGAppRefreshTaskRequest(identifier:"") and .backgroundTask(.appRefresh) APIs.

BGAppRefreshTaskRequest is not supported in watchOS, as shown in the availability session here, and so Xcode doesn't have the Background fetch or Background processing for an watchOS app.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

With option 1, this update opportunity is great as it will specifically update when there's new steps so even once an hour this would be helpful

Yeah, I'd say option 1 is the right way to go in your case.

But I can't determine if this update takes away one of the standard timeline expiration updates that already run 4 times an hour?

If reloadAllTimelines is called when your watchOS app is running in the foreground, the reload won't be counted against the budget; otherwise, it will. This is discussed in Keeping a widget up to date.

Could I observe additional Health types to get additional updates?

Yes, though an additional data type can change at a different pace, and hence your app can be woken up more frequently. This is somehow discussed in this post.

Do I need the Background Modes Capability as well as the HealthKit Background Delivery for this in Xcode or just the HealthKit one?

I don't think you need any background mode other than HealthKit Background Delivery.

WatchOS also support the BGAppRefreshTaskRequest(identifier:"") and .backgroundTask(.appRefresh) APIs.

BGAppRefreshTaskRequest is not supported in watchOS, as shown in the availability session here, and so Xcode doesn't have the Background fetch or Background processing for an watchOS app.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Hi Ziqiao,

Thank you very much for the reply and details on this issue. This information is helpful. If I could just specifically clarify a couple of points just so it's really clear.

When the background HealthKit observer query does wake my Watch app in the background and I call WidgetCenter.shared.reloadAllTimelines() before the completion handler, does this remove one of my separate widget extension budget's 4 hourly reloads?

With regards to BGAppRefreshTaskRequest, I am confused here. Xcode does offer some supported modes to use this with Watch apps and the link you provided does show availability from WatchOS 6.0+.... Sorry I'm probably missing something here!

Many thanks

Accepted Answer

I am confused here. Xcode does offer some supported modes to use this with Watch apps and the link you provided does show availability from WatchOS 6.0+

Thanks for pointing out that BGAppRefreshTaskRequest is documented as available on watchOS. I'm pretty sure that is a documentation bug (and have filed a feedback report against it) because:

  • The BackgroundTasks framework is unavailable on watchOS. You can't import it in a watchOS app.
  • The Background fetch or Background processing modes are not supported in watchOS either. as described here.

On watchOS, the counterpart of BGAppRefreshTask is WKRefreshBackgroundTask. How to use the API is covered in Using background tasks.

When you schedule a background refresh task (WKRefreshBackgroundTask on watchOS, or BGAppRefreshTask on iOS), the system guarantees to not trigger the task earlier than the preferred fire date. However, it may delay the task based on its current state and if your app exceeds its budget. As a result, you may see that a scheduled task is triggered much later, or isn't even triggered, which I believe doesn't fit your use case.

When the background HealthKit observer query does wake my Watch app in the background and I call WidgetCenter.shared.reloadAllTimelines() before the completion handler, does this remove one of my separate widget extension budget's 4 hourly reloads?

In this case, your app is running in the background, and so the reload is counted against the budget, according to the documentation.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Maximise background update on WatchOS
 
 
Q