0

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!

Krishan Madushanka
  • 319
  • 1
  • 5
  • 21
  • 1
    You can't use `@ObservableObject` outside of a SwiftUI View. Also your `createJSON` function uses `hourlyStpCount` immediately after calling `readHourlyStepCount`, but the HealthKit query is going to complete asynchronously at some later time. You could use a Combine subscriber to obtain the step count once it is available. – Paulw11 Dec 19 '22 at 09:12

1 Answers1

1

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.

Jorge Poveda
  • 161
  • 6
  • yep definitely, should have done the double-check. Already edited. Thanks for the feedback. – Jorge Poveda Dec 19 '22 at 10:27
  • This solution works. But say there are multiple methods like `readHourlyStepCount`, In that case I need to take result from all those methods and build the JSON.Any idea how to do that? – Krishan Madushanka Dec 19 '22 at 13:36
  • Do you mean that you have more than one async call and you need to "make something" when they all finish? If that's what you are saying, you should look for "how to handle multiple asynchronous calls" in Google. If you still have problems after researching, you can post another question with a new title, description,... PS: There are many approaches to that as well, personally, I would use Combine's zip functionality though. – Jorge Poveda Dec 20 '22 at 09:00
  • Yeah that is the second question I have but as you said I should ask it in a separate question. Anyway I used `DispatchGroup` to solve that problem. – Krishan Madushanka Dec 21 '22 at 15:08