Thank you for this! I have a very similar use case where we need to upload heath data nightly. I just changed our implementation to use BGProcessingTaskRequest, however it still didn't seem to work. We use SwiftUI. Please note that the following code is written for testing, so it is scheduled to be 1 minute out. And since I am running this often, I have the App Delegate cancel previous tasks before registering/scheduling a new one. In real life, we will have it scheduled for 3 AM daily, and the app delegate will check if a task is already scheduled and, if not, schedule it. Also note that we have user consent and permissions enabled and this process is handled elsewhere.
In our AppDelegate class, we have:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
self.healthKitStuff()
return true
}
func healthKitStuff() {
BGTaskScheduler.shared.getPendingTaskRequests { result in
let alreadyScheduled = result.contains { $0.identifier == Tasks.healthKitDataSync.rawValue }
if !alreadyScheduled {
HealthKit.shared.registerHealthKitSyncJob()
HealthKit.shared.scheduleHealthKitSyncJob()
} else {
HealthKit.shared.cancelHealthKitSyncJob()
self.healthKitStuff()
}
}
}
In our HealthKit.swift file:
import Foundation
import BackgroundTasks
import HealthKit
import SwiftUI
class HealthKit: NSObject {
static let shared = HealthKit()
// identifier defined in info.plist -> 'Permitted background task scheduler identifiers'
private var isSupported: Bool {
return HKHealthStore.isHealthDataAvailable()
}
}
// Sync HealthKit data with server
extension HealthKit {
func uploadYesterdaysHealthData(completionHandler: @escaping () -> Void) {
print("xxx7 in uploadYesterdaysHealthData")
fetchYesterdaysHealthData() { input in
print("input is : \(input)")
let mutation = SaveFitnessDataMutation(input: input)
Api.shared.apollo.perform(mutation: mutation) { result in
completionHandler()
switch result {
case .success(let data):
if let resultData = data.data {
print("START printing result data")
print(resultData.saveUserFitness.success)
print(resultData.saveUserFitness.message)
print("END printing result data")
}
if let errors = data.errors {
print("Error! : \(errors)")
}
case .failure(let error):
print("Error! : \(error)")
}
}
}
}
func registerHealthKitSyncJob() {
print("xxx4 in registerHealthKitSyncJob")
BGTaskScheduler.shared.register(forTaskWithIdentifier: Tasks.healthKitDataSync.rawValue, using: nil) { task in
print("xxx5 task is: \(task)")
HealthKit.shared.uploadYesterdaysHealthData() {
print("xxx6 task completed")
task.setTaskCompleted(success: true)
// call the job again to run the next day
print("calling scheduleHealthKitSyncJob from register")
self.scheduleHealthKitSyncJob()
}
}
}
func cancelHealthKitSyncJob() {
print("CALLING CANCEL")
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: Tasks.healthKitDataSync.rawValue)
}
func scheduleHealthKitSyncJob() {
print("zzz3 in scheduleHealthKitSyncJob")
//let request = BGAppRefreshTaskRequest(identifier: Tasks.healthKitDataSync.rawValue)
let request = BGProcessingTaskRequest(identifier: Tasks.healthKitDataSync.rawValue)
print("request is \(request)")
var runAt: Date {
let calendar = Calendar.current
let startOfDay = calendar.startOfDay(for: Date())
let tomorrow = calendar.date(byAdding: .day, value: 1, to: startOfDay)
let runAt = Date().adding(minutes: 1) //calendar.date(byAdding: .hour, value: 3, to: tomorrow!)
// Scheduling job to run day after user consent since we look at previous day's data
print("runAt is: \(String(describing: runAt))")
return runAt
}
request.earliestBeginDate = runAt
do {
try BGTaskScheduler.shared.submit(request)
print("submitted task request")
} catch {
print("Could not schedule HealthKit Sync Job: \(error)")
}
print("zzz3 END scheduleHealthKitSyncJob")
}
}
extension Date {
func adding(minutes: Int) -> Date {
return Calendar.current.date(byAdding: .minute, value: minutes, to: self)!
}
}
Thank you!
Topic:
App & System Services
SubTopic:
Networking
Tags: