From 2109de07dab4a1fb4516dee1b18becceeba1e3eb Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 14 Feb 2024 10:21:44 +0000 Subject: [PATCH 1/4] Added getTotalCaloriesInInterval and getTotalDistanceInInterval methods for Android. --- .../cachet/plugins/health/HealthPlugin.kt | 245 ++++++++++++++---- packages/health/lib/src/health_factory.dart | 30 +++ 2 files changed, 221 insertions(+), 54 deletions(-) diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt index 424f0afea..3a12ff14a 100644 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt @@ -13,6 +13,7 @@ import androidx.annotation.NonNull import androidx.core.content.ContextCompat import androidx.health.connect.client.HealthConnectClient import androidx.health.connect.client.PermissionController +import androidx.health.connect.client.aggregate.AggregateMetric import androidx.health.connect.client.permission.HealthPermission import androidx.health.connect.client.records.* import androidx.health.connect.client.records.MealType.MEAL_TYPE_BREAKFAST @@ -73,7 +74,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : private var context: Context? = null private var threadPoolExecutor: ExecutorService? = null private var useHealthConnectIfAvailable: Boolean = false - private var healthConnectRequestPermissionsLauncher: ActivityResultLauncher>? = null + private var healthConnectRequestPermissionsLauncher: ActivityResultLauncher>? = null private lateinit var healthConnectClient: HealthConnectClient private lateinit var scope: CoroutineScope @@ -83,6 +84,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : private var STEPS = "STEPS" private var AGGREGATE_STEP_COUNT = "AGGREGATE_STEP_COUNT" private var ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED" + private var AGGREGATE_CALORIES_COUNT = "AGGREGATE_CALORIES_COUNT" private var HEART_RATE = "HEART_RATE" private var BODY_TEMPERATURE = "BODY_TEMPERATURE" private var BLOOD_PRESSURE_SYSTOLIC = "BLOOD_PRESSURE_SYSTOLIC" @@ -91,6 +93,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : private var BLOOD_GLUCOSE = "BLOOD_GLUCOSE" private var MOVE_MINUTES = "MOVE_MINUTES" private var DISTANCE_DELTA = "DISTANCE_DELTA" + private var AGGREGATE_DISTANCE_COUNT = "AGGREGATE_DISTANCE_COUNT" private var WATER = "WATER" private var RESTING_HEART_RATE = "RESTING_HEART_RATE" private var BASAL_ENERGY_BURNED = "BASAL_ENERGY_BURNED" @@ -418,19 +421,18 @@ class HealthPlugin(private var channel: MethodChannel? = null) : } - private fun onHealthConnectPermissionCallback(permissionGranted: Set) - { - if(permissionGranted.isEmpty()) { + private fun onHealthConnectPermissionCallback(permissionGranted: Set) { + if (permissionGranted.isEmpty()) { mResult?.success(false); Log.i("FLUTTER_HEALTH", "Access Denied (to Health Connect)!") - }else { + } else { mResult?.success(true); Log.i("FLUTTER_HEALTH", "Access Granted (to Health Connect)!") } } - + private fun keyToHealthDataType(type: String): DataType { return when (type) { BODY_FAT_PERCENTAGE -> DataType.TYPE_BODY_FAT_PERCENTAGE @@ -439,6 +441,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : STEPS -> DataType.TYPE_STEP_COUNT_DELTA AGGREGATE_STEP_COUNT -> DataType.AGGREGATE_STEP_COUNT_DELTA ACTIVE_ENERGY_BURNED -> DataType.TYPE_CALORIES_EXPENDED + AGGREGATE_CALORIES_COUNT -> DataType.AGGREGATE_CALORIES_EXPENDED HEART_RATE -> DataType.TYPE_HEART_RATE_BPM BODY_TEMPERATURE -> HealthDataTypes.TYPE_BODY_TEMPERATURE BLOOD_PRESSURE_SYSTOLIC -> HealthDataTypes.TYPE_BLOOD_PRESSURE @@ -447,6 +450,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : BLOOD_GLUCOSE -> HealthDataTypes.TYPE_BLOOD_GLUCOSE MOVE_MINUTES -> DataType.TYPE_MOVE_MINUTES DISTANCE_DELTA -> DataType.TYPE_DISTANCE_DELTA + AGGREGATE_DISTANCE_COUNT -> DataType.AGGREGATE_DISTANCE_DELTA WATER -> DataType.TYPE_HYDRATION SLEEP_ASLEEP -> DataType.TYPE_SLEEP_SEGMENT SLEEP_AWAKE -> DataType.TYPE_SLEEP_SEGMENT @@ -662,7 +666,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : } } - /** + /** * Save a Nutrition measurement with calories, carbs, protein, fat, name and mealType */ private fun writeMeal(call: MethodCall, result: Result) { @@ -1451,10 +1455,62 @@ class HealthPlugin(private var channel: MethodChannel? = null) : return } + getTotalInInterval(start, end, result, STEPS, AGGREGATE_STEP_COUNT, "estimated_steps", true) + } + + private fun getTotalCaloriesInInterval(call: MethodCall, result: Result) { + val start = call.argument("startTime")!! + val end = call.argument("endTime")!! + + if (useHealthConnectIfAvailable && healthConnectAvailable) { + getCaloriesHealthConnect(start, end, result) + return + } + + getTotalInInterval( + start, + end, + result, + ACTIVE_ENERGY_BURNED, + AGGREGATE_CALORIES_COUNT, + "calories", + false + ) + } + + private fun getTotalDistanceInInterval(call: MethodCall, result: Result) { + val start = call.argument("startTime")!! + val end = call.argument("endTime")!! + + if (useHealthConnectIfAvailable && healthConnectAvailable) { + getDistanceHealthConnect(start, end, result) + return + } + + getTotalInInterval( + start, + end, + result, + DISTANCE_DELTA, + AGGREGATE_DISTANCE_COUNT, + "distance", + false + ) + } + + private fun getTotalInInterval( + start: Long, + end: Long, + result: Result, + dataType: String, + aggregateDataType: String, + streamName: String, + asInt: Boolean + ) { val context = context ?: return - val stepsDataType = keyToHealthDataType(STEPS) - val aggregatedDataType = keyToHealthDataType(AGGREGATE_STEP_COUNT) + val stepsDataType = keyToHealthDataType(dataType) + val aggregatedDataType = keyToHealthDataType(aggregateDataType) val fitnessOptions = FitnessOptions.builder() .addDataType(stepsDataType) @@ -1466,7 +1522,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : .setAppPackageName("com.google.android.gms") .setDataType(stepsDataType) .setType(DataSource.TYPE_DERIVED) - .setStreamName("estimated_steps") + .setStreamName(streamName) .build() val duration = (end - start).toInt() @@ -1481,43 +1537,84 @@ class HealthPlugin(private var channel: MethodChannel? = null) : .addOnFailureListener( errHandler( result, - "There was an error getting the total steps in the interval!", + "There was an error getting the total $dataType in the interval!", ), ) .addOnSuccessListener( threadPoolExecutor!!, - getStepsInRange(start, end, aggregatedDataType, result), + getAggregatedInRange(start, end, aggregatedDataType, result, asInt), ) } private fun getStepsHealthConnect(start: Long, end: Long, result: Result) = scope.launch { try { - val startInstant = Instant.ofEpochMilli(start) - val endInstant = Instant.ofEpochMilli(end) - val response = healthConnectClient.aggregate( - AggregateRequest( - metrics = setOf(StepsRecord.COUNT_TOTAL), - timeRangeFilter = TimeRangeFilter.between(startInstant, endInstant), - ), - ) - // The result may be null if no data is available in the time range. - val stepsInInterval = response[StepsRecord.COUNT_TOTAL] ?: 0L + val stepsInInterval = + getAggregatedHealthConnectMetric(start, end, StepsRecord.COUNT_TOTAL) ?: 0L Log.i("FLUTTER_HEALTH::SUCCESS", "returning $stepsInInterval steps") result.success(stepsInInterval) } catch (e: Exception) { - Log.i("FLUTTER_HEALTH::ERROR", "unable to return steps") + Log.i("FLUTTER_HEALTH::ERROR", "unable to return steps, $e") result.success(null) } } - private fun getStepsInRange( + private fun getCaloriesHealthConnect(start: Long, end: Long, result: Result) = + scope.launch { + try { + val caloriesInInterval = getAggregatedHealthConnectMetric( + start, + end, + TotalCaloriesBurnedRecord.ENERGY_TOTAL + )?.inKilocalories ?: 0.0 + Log.i("FLUTTER_HEALTH::SUCCESS", "returning $caloriesInInterval kilocalories") + result.success(caloriesInInterval) + } catch (e: Exception) { + Log.i("FLUTTER_HEALTH::ERROR", "unable to return kilocalories, $e") + result.success(null) + } + } + + private fun getDistanceHealthConnect(start: Long, end: Long, result: Result) = + scope.launch { + try { + val distanceInInterval = getAggregatedHealthConnectMetric( + start, + end, + DistanceRecord.DISTANCE_TOTAL + )?.inKilometers ?: 0.0 + Log.i("FLUTTER_HEALTH::SUCCESS", "returning $distanceInInterval km") + result.success(distanceInInterval) + } catch (e: Exception) { + Log.i("FLUTTER_HEALTH::ERROR", "unable to return distance, $e") + result.success(null) + } + } + + suspend fun getAggregatedHealthConnectMetric( + start: Long, + end: Long, + recordMetric: AggregateMetric + ): T? { + val startInstant = Instant.ofEpochMilli(start) + val endInstant = Instant.ofEpochMilli(end) + val response = healthConnectClient.aggregate( + AggregateRequest( + metrics = setOf(recordMetric), + timeRangeFilter = TimeRangeFilter.between(startInstant, endInstant), + ), + ) + return response[recordMetric] + } + + private fun getAggregatedInRange( start: Long, end: Long, aggregatedDataType: DataType, result: Result, + asInt: Boolean ) = OnSuccessListener { response: DataReadResponse -> - val map = HashMap() // need to return to Dart so can't use sparse array + val map = HashMap() // need to return to Dart so can't use sparse array for (bucket in response.buckets) { val dp = bucket.dataSets.firstOrNull()?.dataPoints?.firstOrNull() if (dp != null) { @@ -1528,17 +1625,17 @@ class HealthPlugin(private var channel: MethodChannel? = null) : val endDate = Date(dp.getEndTime(TimeUnit.MILLISECONDS)) Log.i( "FLUTTER_HEALTH::SUCCESS", - "returning $count steps for $startDate - $endDate", + "returning $count for $startDate - $endDate", ) - map[startTime] = count.asInt() + map[startTime] = if (asInt) count.asInt() else count.asFloat() } else { val startDay = Date(start) val endDay = Date(end) - Log.i("FLUTTER_HEALTH::ERROR", "no steps for $startDay - $endDay") + Log.i("FLUTTER_HEALTH::ERROR", "no data for $startDay - $endDay") } } - assert(map.size <= 1) { "getTotalStepsInInterval should return only one interval. Found: ${map.size}" } + assert(map.size <= 1) { "getTotalInInterval should return only one interval. Found: ${map.size}" } Handler(context!!.mainLooper).run { result.success(map.values.firstOrNull()) } @@ -1561,6 +1658,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : "writeData" -> writeData(call, result) "delete" -> delete(call, result) "getTotalStepsInInterval" -> getTotalStepsInInterval(call, result) + "getTotalCaloriesInInterval" -> getTotalCaloriesInInterval(call, result) + "getTotalDistanceInInterval" -> getTotalDistanceInInterval(call, result) "writeWorkoutData" -> writeWorkoutData(call, result) "writeBloodPressure" -> writeBloodPressure(call, result) "writeBloodOxygen" -> writeBloodOxygen(call, result) @@ -1577,12 +1676,16 @@ class HealthPlugin(private var channel: MethodChannel? = null) : activity = binding.activity - if ( healthConnectAvailable) { - val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract() - - healthConnectRequestPermissionsLauncher =(activity as ComponentActivity).registerForActivityResult(requestPermissionActivityContract) { granted -> - onHealthConnectPermissionCallback(granted); - } + if (healthConnectAvailable) { + val requestPermissionActivityContract = + PermissionController.createRequestPermissionResultContract() + + healthConnectRequestPermissionsLauncher = + (activity as ComponentActivity).registerForActivityResult( + requestPermissionActivityContract + ) { granted -> + onHealthConnectPermissionCallback(granted); + } } } @@ -1626,7 +1729,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : var permList = mutableListOf() for ((i, typeKey) in types.withIndex()) { - if(!MapToHCType.containsKey(typeKey)) { + if (!MapToHCType.containsKey(typeKey)) { Log.w("FLUTTER_HEALTH::ERROR", "Datatype " + typeKey + " not found in HC") result.success(false) return @@ -1681,7 +1784,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : var permList = mutableListOf() for ((i, typeKey) in types.withIndex()) { - if(!MapToHCType.containsKey(typeKey)) { + if (!MapToHCType.containsKey(typeKey)) { Log.w("FLUTTER_HEALTH::ERROR", "Datatype " + typeKey + " not found in HC") result.success(false) return @@ -1721,8 +1824,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : } } } - - if(healthConnectRequestPermissionsLauncher == null) { + + if (healthConnectRequestPermissionsLauncher == null) { result.success(false) Log.i("FLUTTER_HEALTH", "Permission launcher not found") return; @@ -1747,10 +1850,10 @@ class HealthPlugin(private var channel: MethodChannel? = null) : // Define the maximum amount of data that HealthConnect can return in a single request timeRangeFilter = TimeRangeFilter.between(startTime, endTime), ) - + var response = healthConnectClient.readRecords(request) var pageToken = response.pageToken - + // Add the records from the initial response to the records list records.addAll(response.records) @@ -1820,15 +1923,13 @@ class HealthPlugin(private var channel: MethodChannel? = null) : ), ) } - // Filter sleep stages for requested stage - } - else if (classType == SleepSessionRecord::class) { + // Filter sleep stages for requested stage + } else if (classType == SleepSessionRecord::class) { for (rec in response.records) { if (rec is SleepSessionRecord) { if (dataType == SLEEP_SESSION) { healthConnectData.addAll(convertRecord(rec, dataType)) - } - else { + } else { for (recStage in rec.stages) { if (dataType == MapSleepStageToType[recStage.stage]) { healthConnectData.addAll( @@ -2154,42 +2255,78 @@ class HealthPlugin(private var channel: MethodChannel? = null) : endTime = Instant.ofEpochMilli(endTime), startZoneOffset = null, endZoneOffset = null, - stages = listOf(SleepSessionRecord.Stage(Instant.ofEpochMilli(startTime), Instant.ofEpochMilli(endTime), SleepSessionRecord.STAGE_TYPE_SLEEPING)), + stages = listOf( + SleepSessionRecord.Stage( + Instant.ofEpochMilli(startTime), + Instant.ofEpochMilli(endTime), + SleepSessionRecord.STAGE_TYPE_SLEEPING + ) + ), ) SLEEP_LIGHT -> SleepSessionRecord( startTime = Instant.ofEpochMilli(startTime), endTime = Instant.ofEpochMilli(endTime), startZoneOffset = null, endZoneOffset = null, - stages = listOf(SleepSessionRecord.Stage(Instant.ofEpochMilli(startTime), Instant.ofEpochMilli(endTime), SleepSessionRecord.STAGE_TYPE_LIGHT)), + stages = listOf( + SleepSessionRecord.Stage( + Instant.ofEpochMilli(startTime), + Instant.ofEpochMilli(endTime), + SleepSessionRecord.STAGE_TYPE_LIGHT + ) + ), ) SLEEP_DEEP -> SleepSessionRecord( startTime = Instant.ofEpochMilli(startTime), endTime = Instant.ofEpochMilli(endTime), startZoneOffset = null, endZoneOffset = null, - stages = listOf(SleepSessionRecord.Stage(Instant.ofEpochMilli(startTime), Instant.ofEpochMilli(endTime), SleepSessionRecord.STAGE_TYPE_DEEP)), + stages = listOf( + SleepSessionRecord.Stage( + Instant.ofEpochMilli(startTime), + Instant.ofEpochMilli(endTime), + SleepSessionRecord.STAGE_TYPE_DEEP + ) + ), ) SLEEP_REM -> SleepSessionRecord( startTime = Instant.ofEpochMilli(startTime), endTime = Instant.ofEpochMilli(endTime), startZoneOffset = null, endZoneOffset = null, - stages = listOf(SleepSessionRecord.Stage(Instant.ofEpochMilli(startTime), Instant.ofEpochMilli(endTime), SleepSessionRecord.STAGE_TYPE_REM)), + stages = listOf( + SleepSessionRecord.Stage( + Instant.ofEpochMilli(startTime), + Instant.ofEpochMilli(endTime), + SleepSessionRecord.STAGE_TYPE_REM + ) + ), ) SLEEP_OUT_OF_BED -> SleepSessionRecord( startTime = Instant.ofEpochMilli(startTime), endTime = Instant.ofEpochMilli(endTime), startZoneOffset = null, endZoneOffset = null, - stages = listOf(SleepSessionRecord.Stage(Instant.ofEpochMilli(startTime), Instant.ofEpochMilli(endTime), SleepSessionRecord.STAGE_TYPE_OUT_OF_BED)), + stages = listOf( + SleepSessionRecord.Stage( + Instant.ofEpochMilli(startTime), + Instant.ofEpochMilli(endTime), + SleepSessionRecord.STAGE_TYPE_OUT_OF_BED + ) + ), ) SLEEP_AWAKE -> SleepSessionRecord( startTime = Instant.ofEpochMilli(startTime), endTime = Instant.ofEpochMilli(endTime), startZoneOffset = null, endZoneOffset = null, - stages = listOf(SleepSessionRecord.Stage(Instant.ofEpochMilli(startTime), Instant.ofEpochMilli(endTime), SleepSessionRecord.STAGE_TYPE_AWAKE)), + stages = listOf( + SleepSessionRecord.Stage( + Instant.ofEpochMilli(startTime), + Instant.ofEpochMilli(endTime), + SleepSessionRecord.STAGE_TYPE_AWAKE + ) + ), ) SLEEP_SESSION -> SleepSessionRecord( startTime = Instant.ofEpochMilli(startTime), @@ -2246,7 +2383,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : val endTime = Instant.ofEpochMilli(call.argument("endTime")!!) val totalEnergyBurned = call.argument("totalEnergyBurned") val totalDistance = call.argument("totalDistance") - if(workoutTypeMapHealthConnect.containsKey(type) == false) { + if (workoutTypeMapHealthConnect.containsKey(type) == false) { result.success(false) Log.w("FLUTTER_HEALTH::ERROR", "[Health Connect] Workout type not supported") return @@ -2344,7 +2481,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : val type = call.argument("dataTypeKey")!! val startTime = Instant.ofEpochMilli(call.argument("startTime")!!) val endTime = Instant.ofEpochMilli(call.argument("endTime")!!) - if(!MapToHCType.containsKey(type)) { + if (!MapToHCType.containsKey(type)) { Log.w("FLUTTER_HEALTH::ERROR", "Datatype " + type + " not found in HC") result.success(false) return diff --git a/packages/health/lib/src/health_factory.dart b/packages/health/lib/src/health_factory.dart index dabd56b1b..bf00ae88b 100644 --- a/packages/health/lib/src/health_factory.dart +++ b/packages/health/lib/src/health_factory.dart @@ -589,6 +589,36 @@ class HealthFactory { return stepsCount; } + Future getTotalCaloriesInInterval( + DateTime startTime, + DateTime endTime, + ) async { + final args = { + 'startTime': startTime.millisecondsSinceEpoch, + 'endTime': endTime.millisecondsSinceEpoch + }; + final calories = await _channel.invokeMethod( + 'getTotalCaloriesInInterval', + args, + ); + return calories; + } + + Future getTotalDistanceInInterval( + DateTime startTime, + DateTime endTime, + ) async { + final args = { + 'startTime': startTime.millisecondsSinceEpoch, + 'endTime': endTime.millisecondsSinceEpoch + }; + final distance = await _channel.invokeMethod( + 'getTotalDistanceInInterval', + args, + ); + return distance; + } + /// Assigns numbers to specific [HealthDataType]s. int _alignValue(HealthDataType type) { switch (type) { From dd79d909fd969db7b40b548a6e0836b6a13558e3 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 14 Feb 2024 14:28:46 +0000 Subject: [PATCH 2/4] Added getTotalCaloriesInInterval and getTotalDistanceInInterval methods for iOS. --- .../ios/Classes/SwiftHealthPlugin.swift | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift index 36d560010..4912dab44 100644 --- a/packages/health/ios/Classes/SwiftHealthPlugin.swift +++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift @@ -148,6 +148,16 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { getTotalStepsInInterval(call: call, result: result) } + /// Handle getTotalCaloriesInInterval + else if call.method.elementsEqual("getTotalCaloriesInInterval") { + getTotalCaloriesInInterval(call: call, result: result) + } + + /// Handle getTotalDistanceInInterval + else if call.method.elementsEqual("getTotalDistanceInInterval") { + getTotalDistanceInInterval(call: call, result: result) + } + /// Handle writeData else if call.method.elementsEqual("writeData") { try! writeData(call: call, result: result) @@ -802,6 +812,18 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { } func getTotalStepsInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) { + getTotalInInterval(call: call, result: result, identifier: HKQuantityTypeIdentifier.stepCount, unit: HKUnit.count(), asInt: true) + } + + func getTotalCaloriesInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) { + getTotalInInterval(call: call, result: result, identifier: HKQuantityTypeIdentifier.activeEnergyBurned, unit: HKUnit.kilocalorie(), asInt: false) + } + + func getTotalDistanceInInterval(call: FlutterMethodCall, result: @escaping FlutterResult) { + getTotalInInterval(call: call, result: result, identifier: HKQuantityTypeIdentifier.distanceWalkingRunning, unit: HKUnit.meter(), asInt: false) + } + + private func getTotalInInterval(call: FlutterMethodCall, result: @escaping FlutterResult, identifier: HKQuantityTypeIdentifier, unit: HKUnit, asInt: Bool) { let arguments = call.arguments as? NSDictionary let startTime = (arguments?["startTime"] as? NSNumber) ?? 0 let endTime = (arguments?["endTime"] as? NSNumber) ?? 0 @@ -810,7 +832,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { let dateFrom = Date(timeIntervalSince1970: startTime.doubleValue / 1000) let dateTo = Date(timeIntervalSince1970: endTime.doubleValue / 1000) - let sampleType = HKQuantityType.quantityType(forIdentifier: .stepCount)! + let sampleType = HKQuantityType.quantityType(forIdentifier: identifier)! let predicate = HKQuery.predicateForSamples( withStart: dateFrom, end: dateTo, options: .strictStartDate) @@ -822,7 +844,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { guard let queryResult = queryResult else { let error = error! as NSError - print("Error getting total steps in interval \(error.localizedDescription)") + print("Error getting total in interval \(error.localizedDescription)") DispatchQueue.main.async { result(nil) @@ -830,16 +852,14 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { return } - var steps = 0.0 + var total = 0.0 if let quantity = queryResult.sumQuantity() { - let unit = HKUnit.count() - steps = quantity.doubleValue(for: unit) + total = quantity.doubleValue(for: unit) } - let totalSteps = Int(steps) DispatchQueue.main.async { - result(totalSteps) + result(asInt ? Int(total) : total) } } From a88ab838b0eed62d36790074e066ad7a0bef4b1c Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 14 Feb 2024 14:34:18 +0000 Subject: [PATCH 3/4] Changed Android method getTotalDistanceInInterval result from kilometers to meters for consistency with iOS. --- .../src/main/kotlin/cachet/plugins/health/HealthPlugin.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt index 3a12ff14a..92f193e84 100644 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt @@ -1581,8 +1581,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : start, end, DistanceRecord.DISTANCE_TOTAL - )?.inKilometers ?: 0.0 - Log.i("FLUTTER_HEALTH::SUCCESS", "returning $distanceInInterval km") + )?.inMeters ?: 0.0 + Log.i("FLUTTER_HEALTH::SUCCESS", "returning $distanceInInterval meters") result.success(distanceInInterval) } catch (e: Exception) { Log.i("FLUTTER_HEALTH::ERROR", "unable to return distance, $e") From 08b414f8e9b4437fad3d0ec6175789cfde8a7436 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 2 Mar 2024 08:45:51 +0000 Subject: [PATCH 4/4] For Android changed stream names: calories to merge_calories_expended and distance to merge_distance_delta. --- .../src/main/kotlin/cachet/plugins/health/HealthPlugin.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt index 92f193e84..3aeb55ed3 100644 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt @@ -1473,7 +1473,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : result, ACTIVE_ENERGY_BURNED, AGGREGATE_CALORIES_COUNT, - "calories", + "merge_calories_expended", false ) } @@ -1493,7 +1493,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : result, DISTANCE_DELTA, AGGREGATE_DISTANCE_COUNT, - "distance", + "merge_distance_delta", false ) }