Hi, I am having some trouble with uploading HealthKit data to AWS S3 in the background. As of now, when I click on the button to beginBackgroundUpdates the data is uploaded as expected. When I go off the app and add data to HealthKit nothing happens. When I go back to the app, the new data is uploaded. I am not sure why this is not happening in the background. More specifically, I am not sure if this is allowed, and if so what I am doing wrong.
ContentView:
import SwiftUI
import HealthKit
struct ContentView: View {
@StateObject var healthKitManager = HealthKitManager()
var body: some View {
VStack {
Button("Enable Background Step Delivery") {
healthKitManager.beginBackgroundUpdates()
}
}
.padding()
.onAppear {
healthKitManager.requestAuthorization { success in
print("Configured HealthKit with Return: \(success)")
}
}
}
}
#Preview {
ContentView()
}
BackgroundDeliveryApp:
import SwiftUI
import HealthKit
import Amplify
import AWSS3StoragePlugin
import AWSCognitoAuthPlugin
@main
struct HKBackgruondDeliveryApp: App {
private func configureAmplify() {
do {
try Amplify.add(plugin: AWSCognitoAuthPlugin())
try Amplify.add(plugin: AWSS3StoragePlugin())
try Amplify.configure()
print("Succesfully configured Amplify with S3 Storage")
} catch {
print("Could not configure Amplify")
}
}
init() {
configureAmplify()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
HealthKitManager
import Foundation
import HealthKit
import Amplify
struct TestStep: Encodable, Decodable {
let count: Double
let startDate: Date
let endDate: Date
let device: String
}
class HealthKitManager: ObservableObject {
var healthStore: HKHealthStore?
let stepType = HKObjectType.quantityType(forIdentifier: .stepCount)
let heartRateType = HKQuantityType(.heartRate)
let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis)
init() {
if HKHealthStore.isHealthDataAvailable() {
healthStore = HKHealthStore()
} else {
print("There is no health data available")
healthStore = nil
}
}
func encodeStepList(stepList: [TestStep]) -> Data{
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
do {
return try encoder.encode(stepList)
} catch {
return Data()
}
}
func uploadStepData(stepList: [TestStep]) async {
let stepData = self.encodeStepList(stepList: stepList)
let uploadTask = Amplify.Storage.uploadData(
key: "ExampleKey",
data: stepData
)
Task {
for await progress in await uploadTask.progress {
print("Progress: \(progress)")
}
}
do {
let value = try await uploadTask.value
print("Completed: \(value)")
} catch {
print("Could not upload step data")
}
}
func requestAuthorization(completion: @escaping (Bool) -> Void) {
guard let stepType = stepType, let sleepType = sleepType else {
return completion(false)
}
guard let healthStore = self.healthStore else {
return completion(false)
}
healthStore.requestAuthorization(toShare: [], read: [stepType, heartRateType, sleepType]) { success, error in
if let error = error {
print("Some error has occoured during authorization of healthKit")
print(error)
}
return completion(success)
}
}
func beginBackgroundUpdates() {
guard let healthStore = healthStore, let stepType = stepType else {
print("Cannot begin background updates because HealthStore is nil")
return
}
healthStore.enableBackgroundDelivery(for: stepType, frequency: .immediate) { success, error in
print("Background update of health data")
if let error = error {
print("Some error has occoured during the set up of the background observer query for steps")
print(error)
return
}
guard let query = self.createObserverQuery() else {
print("Could not create a query for steps")
return
}
healthStore.execute(query)
}
}
func stepCountDeviceRecordsQuery(stepCountObjects: @escaping ([TestStep]) -> Void) {
guard let stepType = stepType else {
print("Nil step type")
return
}
let stepCountUnit = HKUnit.count()
let endDate = Date()
let startDate = Calendar.current.date(byAdding: .day, value: -7, to: endDate)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate)
let sortDescriptors = [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)]
let stepCountQuery = HKSampleQuery(sampleType: stepType, predicate: predicate, limit: 10000, sortDescriptors: sortDescriptors) { query, results, error in
if let error = error {
print("Error in getStepCount")
print(error)
return
}
guard let results = results else {
print("Empty results in getStepCount")
return
}
var stepCounts: [TestStep] = []
for (_, record) in results.enumerated() {
guard let record: HKQuantitySample = record as? HKQuantitySample else {return}
let step = TestStep(count: record.quantity.doubleValue(for: stepCountUnit), startDate: record.startDate, endDate: record.endDate, device: record.device?.model ?? "")
stepCounts.append(step)
}
print("\(stepCounts.count) records at \(Date())")
print(stepCounts[stepCounts.count - 1])
stepCountObjects(stepCounts)
}
healthStore?.execute(stepCountQuery)
}
private func createObserverQuery() -> HKQuery? {
guard let stepType = stepType else {
return nil
}
let query = HKObserverQuery(sampleType: stepType, predicate: nil) { query, completionHandler, error in
self.stepCountDeviceRecordsQuery { stepList in
Task {
await self.uploadStepData(stepList: stepList)
}
}
completionHandler()
}
return query
}
}