Swift : Publishing data from one class to another not working (Apple HealthKit)

91 Views Asked by At

class HealthKitQueryBuilder

import Foundation
import HealthKit

class HealthKitQueryBuilder:ObservableObject {
    
    let healthStore: HKHealthStore
    let dateFormatter = DateFormatter()
    
    @Published var hourlyStpCount: [HealthData]?
    
    init(healthStore: HKHealthStore) {
        self.healthStore = healthStore
    }

func readHourlyStepCount(){
        dateFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
        var hourlyStepCount = [HealthData]()
        guard let stepCountType = HKObjectType.quantityType(forIdentifier: .stepCount) else {
            fatalError("*** Unable to get the step count type ***")
        }

        var interval = DateComponents()
        interval.hour = 1

        let calendar = Calendar.current
        let anchorDate = calendar.date(bySettingHour: 0, minute: 55, second: 0, of: Date())

        let query = HKStatisticsCollectionQuery.init(quantityType: stepCountType,
                                                     quantitySamplePredicate: nil,
                                                     options: .cumulativeSum,
                                                     anchorDate: anchorDate!,
                                                     intervalComponents: interval)

        query.initialResultsHandler = { query, results, error in
            let startDate = calendar.date(byAdding: .hour,value: -24, to: Date())
            DispatchQueue.main.async {
            results?.enumerateStatistics(from: startDate!,to: Date(), with: { (result, stop) in

                    hourlyStepCount.append(HealthData(unit: "count", startDate: self.dateFormatter.string(from: result.startDate) , endDate: self.dateFormatter.string(from: result.endDate), value: (result.sumQuantity()?.doubleValue(for: HKUnit.count()) ?? 0)))


            })
                print("Hourly step count : \(hourlyStepCount)")
                self.hourlyStpCount = hourlyStepCount
            }

        }
healthStore.execute(query)
}
}

class DataPointsJSONBuilder

import Foundation
import HealthKit
import SwiftUI

class DataPointsJSONBuilder {
    
    let healthStore: HKHealthStore
    @ObservedObject var queryBuilder: HealthKitQueryBuilder
    
    init(healthStore: HKHealthStore) {
        self.healthStore = healthStore
        self.queryBuilder = HealthKitQueryBuilder(healthStore: healthStore)
    }
    
    func createJSON() ->String?{
        queryBuilder.readHourlyStepCount()
        let totalStepCount = queryBuilder.hourlyStpCount
        let averageRestingHeartRate = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        let averageHeartRateVariability = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        let averageRespiratoryRate = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        let totalSleepDuration = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        let heartRate = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        let systolicBloodPressure = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        let diastolicBloodPressure = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        let oxygenSaturation = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        let currentGlucose = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        let averageGlucose = [HealthData(unit: "count", startDate: "2022-11-22 10:55:00 PM +0000", endDate: "2022-11-22 11:55:00 PM +0000", value: 100.0)]
        
        let dataPoints = DataPointsObj(totalStepCount: totalStepCount, averageRestingHeartRate: [], averaHeartRatevariability: [], averageRespiratoryRate: [], totalSleepDuration: [], heartRate: [], systolicBloodPressure: [], diastolicBloodPressure: [], oxygenSaturation: [], currentGlucoseValue: [], averageGlucoseValue: [])
        
        guard let JSON = encodeToJSON(dataPointsObj: dataPoints) else {
            return nil
        }
        return JSON
    }
    
    private func encodeToJSON(dataPointsObj: DataPointsObj) -> String? {
        let encoder = JSONEncoder()
        encoder.outputFormatting = .withoutEscapingSlashes
        do {
            let result = try encoder.encode(dataPointsObj)
            if let jsonString = String(data: result, encoding: .utf8){
                return jsonString
            }
            return nil
        } catch {
            return nil
        }
    }
}

I have above 2 classes implemented to read data from apple health kit and make a json object to send it to backend. But after the data is fetched data is not published to DataPointsJSONBuilder class. I get data printed inside HealthKitQueryBuilder class successfully and I have added the code for step count only for now. I call the createJSON function inside onAppear in the UI as follows.

.onAppear(){

print(DataPointsJSONBuilder(healthStore: healthStore).createJSON()!)

}

What I want to do is read data from healthkit and bring them to DataPointsJSONBuilder class to send it to backend through a REST api. I don't understand why I hourlyStpCount is not updated when data is read. If there is a wrong implementation here, kindly correct me or suggest a method to solve my problem. Thanks!

1

There are 1 best solutions below

4
On BEST ANSWER

I'd say that the problem here is that you are trying to use an asynchronous call as synchronous.

You use

 print(DataPointsJSONBuilder(healthStore: healthStore).createJSON()!)

The createJSON() function does have a return. Instead it should have an async approach. There are many ways to implement this - you tried to use one, the @Published functionality for example - but to make it simple, I am posting the way using callbacks (completion handlers).

You'll need to change a relatively large piece of code, but to make my point, at the end it should be something like:

func readHourlyStepCount(callback: ([HealthData]) -> ()) {
//implementation
   callback(hourlyStepCount)
//...
}

In the DataPointsJSONBuilder:

func createJSON(callback: (String?) -> ()) {
//implementation
   callback(JSON)
//...
}

And finally:

DataPointsJSONBuilder(healthStore: healthStore).createJSON { json in
   print(json)
}

PS: Maybe you need to add the @escaping tag to the callbacks.