I have an application that need to track user heart rate readings from apple watch, so I did all the required steps that I found on apple guides, and here is the code that I am using:
static var query: HKObserverQuery?
func startObservingHeartRate() {
guard let heartRateSampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else {
fatalError("Unable to create a step count sample type")
}
AppDelegate.query = HKObserverQuery(sampleType: heartRateSampleType, predicate: nil, updateHandler: { (query, completionHandler, error) in
if error != nil {
// Perform Proper Error Handling Here...
print("An error occured while setting up the Heart Rate observer.")
}
//Read the last strored heatt rate in add it to the DB
//Add last fetched Heart Rate reading to DB and send it to clips
HealthKitManager().fetchLastStoredHeartRate(completion: { (lastReading, error) in
guard let lastReading = lastReading else {
//There is no heart readings in HealthKit
return
}
//Check if Last HR value is Abnormal
if lastReading.doubleValue > 60 {
//TODO: - Schedule notification
if UIApplication.shared.applicationState == .background {
} else {
//TODO: - Show popup to the user
}
}
})
completionHandler()
})
healthKitStore.execute(AppDelegate.query!)
configureHeartRateObserver()
}
func configureHeartRateObserver() {
guard let heartRateSampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else {
fatalError("Unable to create a step count sample type")
}
healthKitStore.enableBackgroundDelivery(for: heartRateSampleType, frequency: HKUpdateFrequency.immediate) { (success, error) in
if success {
print("Enabled background delivery of Heart Rate changes")
} else {
print("Failed to enable background delivery of weight changes. ")
}
}
}
and I am calling "startObservingHeartRate" in didFinishLaunchingWithOptions in AppDelegate, assuming that this query should be executed once a new reading added or deleted from the health kit store, every thing is fine, if app is in background or killed the handler wake up my app and it do the updates.
But whenever I put the app in background then put it in foreground again it execute the observer query for many times even if there is no new readings added to the HealthKit store and in this case I am getting the same last heart rate for many times for no reason.
Please any recommendation on how to use this types of query or any changes I need to do with my current implementation.
If you want to track added and removed heart rate samples more precisely, you should use an HKAnchoredObjectQuery. HKObserverQuery does not guarantee that its update handler will only be called when a sample is added or removed. Note that you must continue executing an HKObserverQuery in addition to HKAnchoredObjectQuery since you are also using enableBackgroundDelivery(for:frequency:completion:).
Related
For my App on Apple Watch I want to observe when a workout in Workout-App was completed, to do a background update of my App and re-render the Complication. But the Query never fires.
My Approach was to use an HKObserverQuery and observe .workoutType(). Documentation mentions:
HealthKit uses workout types to create samples that store information about individual workouts.
On https://developer.apple.com/documentation/healthkit/hkobjecttype/1615132-workouttype. So that felt like the right sampleType.
I added com.apple.developer.healthkit.background-delivery to the Entitlements of the WatchApp Extension and Workout access permissions are setup properly.
That is the code, I am using:
let query = HKObserverQuery(sampleType: .workoutType(), predicate: nil, updateHandler: { [weak self] _, completionHandler, error in
guard error == nil else {
// CoreInsights.logs.track("HK Query Error", level: .error)
return
}
// CoreInsights.logs.track("Update Trigger HK", level: .info)
// self?.performBackgroundTasks(completion: {
// completionHandler()
// })
})
self?.healthStore.execute(query)
self?.healthStore.enableBackgroundDelivery(for: .workoutType(), frequency: .immediate, withCompletion: { updateSuccess, _ in
// CoreInsights.logs.track("HK Background \(updateSuccess)", level: .info)
})
healthStore.enableBackgroundDelivery returns with success true.
Someone knows, if I am using the right type and setup background correctly?
So I have been trying to use the MusicKit APIs for a few days now. I have been attempting to use the MPMusicPlayerApplicationController and MutableQueue APIs.
I have queue initialized already using setQueue(with: [String]) with an array of store identifiers for Apple Music songs. Then I want to allow the user to reorder the songs in the queue. I use the following code to attempt that.
let musicPlayerController = MPMusicPlayerController.applicationQueuePlayer
musicPlayerController.perform(queueTransaction: { queue in
let afterItem = queue.items.first(where: { $0.playbackStoreID == predecessorId })
let descriptor = MPMusicPlayerStoreQueueDescriptor(storeIDs: [newItemId])
queue.insert(descriptor, after: afterItem)
}) { (queue, error) in
// Completion for when items' position update
if error != nil {
print(error!)
}
}
The code above works as expected if afterItem is nil (i.e. the song is correctly inserted at the front of the queue). However, if afterItem is not nil, nothing happens. The queue stays the exact same as if no insert happened and there is no error provided in the completion handler. This problem happens regardless of whether the song being inserted is already in the queue or not.
Am I attempting modifying the queue incorrectly?
Ok, I found the solution.
If you want the queue to be mutated.
You need to return the query
let musicPlayerController = MPMusicPlayerController.applicationQueuePlayer
musicPlayerController.perform(queueTransaction: { queue in
let afterItem = queue.items.first(where: { $0.playbackStoreID == predecessorId })
let descriptor = MPMusicPlayerStoreQueueDescriptor(storeIDs: [newItemId])
//return the modification here.
return queue.insert(descriptor, after: afterItem)
}) { (queue, error) in
// Completion for when items' position update
if error != nil {
print(error!)
}
}
I need to get users activity on background using Core Motion. I was able to reach this goal by adding the locations update as background mode and enable the background location fetch. But as I'm not interest in the user location, this results in waste of battery and a blue sign ( iPhone X ) that indicates the app is updating your locations in background.
Is possible to run core motion in background without update the location of the user in order to use less battery and not show the blue sign to the user??
Thank you really much in advance!!
EDIT
Example function code:
private func startTrackingActivityType() {
activityManager.startActivityUpdates(to: OperationQueue.main) {
[weak self] (activity: CMMotionActivity?) in
guard let activity = activity else { return }
DispatchQueue.main.async {
if activity.walking {
self?.activityTypeLabel.text = "Walking"
} else if activity.stationary {
self?.activityTypeLabel.text = "Stationary"
} else if activity.running {
self?.activityTypeLabel.text = "Running"
} else if activity.automotive {
self?.activityTypeLabel.text = "Automotive"
}
}
}
}
You don't need to be in background at all. Next time your app will launch, you can retrieve history for MotionActivity. In addition you can also get raw historical sensor data. But you should schedule it.
Details: https://developer.apple.com/documentation/coremotion/cmmotionactivitymanager?language=objc
Is there a way or delegate to catch the updated values while the app is running without terminating the app. I am using this method to fetch the values and update.
RemoteConfig.remoteConfig().fetch(withExpirationDuration: duration) { [weak self] (status, error) in
guard error == nil else {
print("Got an error fetching remote values \(error!)")
return
}
print ("Retrieved values from the cloud!")
RemoteConfig.remoteConfig().activateFetched()
guard let strongSelf = self else { return }
strongSelf.updateWithRomteConfigValues()
}
The activateFetched() call will make sure to get the latest update data (either from the defaults or the remote config) without needing to terminate the app.
I think the problem in your case come from the duration.
Try setting the duration to 0 (make sure to only do it if the developer mode is enabled )
I need my app to sync between HealthKit and our database while it's in the background. I just can't wrap my head around the logic that determines how and when the HKObserverQueries run their updateHandlers. I need data of various different sample types, so I assume I need an observer query for each one. Right?
According to Apple about function enableBackgroundDeliveryForType, "HealthKit wakes your app whenever new samples of the specified type are saved to the store." But if I enable background deliveries and execute observerqueries for, say, blood glucose and weight, they both seem to run their updatehandlers whenever I input data in either one of them in the Health app. This also seems to happen even when I enable background delivery for only one of the sample types. Why?
func startObserving(completion: ((success: Bool) -> Void)!) {
let sampleTypeBloodGlucose = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)!
let sampleTypeWeight = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass)!
// Enable background delivery for blood glucose
self.healthKitStore.enableBackgroundDeliveryForType(sampleTypeBloodGlucose, frequency: .Immediate) {
(success, error) in
if error != nil {
abort()
}
}
// Enable background delivery for weight
self.healthKitStore.enableBackgroundDeliveryForType(sampleTypeWeight, frequency: .Immediate) {
(success, error) in
if error != nil {
abort()
}
}
// Define update handlers for background deliveries
let updateHandlerBloodGlucose: (HKObserverQuery, HKObserverQueryCompletionHandler, NSError?) -> Void = {
query, completionHandler, error in
if error != nil {
abort()
}
// Handle data and call the completionHandler
completionHandler()
}
let updateHandlerWeight: (HKObserverQuery, HKObserverQueryCompletionHandler, NSError?) -> Void = {
query, completionHandler, error in
if error != nil {
abort()
}
// Handle data and call the completionHandler
completionHandler()
}
let observerQueryBloodGlucose = HKObserverQuery(sampleType: sampleTypeBloodGlucose, predicate: nil, updateHandler: updateHandlerBloodGlucose)
healthKitStore.executeQuery(observerQueryBloodGlucose)
let observerQueryWeight = HKObserverQuery(sampleType: sampleTypeWeight, predicate: nil, updateHandler: updateHandlerWeight)
healthKitStore.executeQuery(observerQueryWeight)
completion(success: true)
}
If you are using the background delivery feature of HealthKit, then yes, you do need to open an HKObserverQuery for each type of data you observe and handle the invocations of the updateHandler and call the provided completion when you are finished. However, the updateHandler of HKObserverQuery is advisory and invocations do not necessarily correspond one-to-one with changes to the HealthKit database (there is not always enough information available to determine what your app has processed and what it hasn't, so sometimes the handler may run when there isn't new data).
Don't worry about understanding or controlling precisely when updateHandler runs - just use it as a trigger to perform other queries that will actually give you up-to-date values from HealthKit. If you need to know precisely which samples in HealthKit are new, for example, then your app should use an HKAnchoredObjectQuery.