diff --git a/packages/health/README.md b/packages/health/README.md index e7cf8c16d..bbf0cd1fe 100644 --- a/packages/health/README.md +++ b/packages/health/README.md @@ -2,8 +2,7 @@ Enables reading and writing health data from/to Apple Health and Health Connect. -> [!IMPORTANT] -> Google has deprecated the Google Fit API. According to the [documentation](https://developers.google.com/fit/android), the API will no longer be available after **June 30, 2025**. As such, this package has removed support for Google Fit as of version 11.0.0 and users are urged to upgrade as soon as possible. +> **NOTE:** Google has deprecated the Google Fit API. According to the [documentation](https://developers.google.com/fit/android), as of **May 1st 2024** developers cannot sign up for using the API. As such, this package has removed support for Google Fit as of version 11.0.0 and users are urged to upgrade as soon as possible. The plugin supports: @@ -26,7 +25,7 @@ See the tables below for supported health and workout data types. ### Apple Health (iOS) -Step 1: Append the `Info.plist` with the following 2 entries +First, add the following 2 entries to the `Info.plist`: ```xml NSHealthShareUsageDescription @@ -35,24 +34,9 @@ Step 1: Append the `Info.plist` with the following 2 entries We will sync your data with the Apple Health app to give you better insights ``` -Step 2: Open your Flutter project in Xcode by right clicking on the "ios" folder and selecting "Open in Xcode". Next, enable "HealthKit" by adding a capability inside the "Signing & Capabilities" tab of the Runner target's settings. +Then, open your Flutter project in Xcode by right clicking on the "ios" folder and selecting "Open in Xcode". Next, enable "HealthKit" by adding a capability inside the "Signing & Capabilities" tab of the Runner target's settings. -### Android - -Starting from API level 28 (Android 9.0) accessing some fitness data (e.g. Steps) requires a special permission. To set it add the following line to your `AndroidManifest.xml` file. - -```xml - -``` - -Additionally, for workouts, if the distance of a workout is requested then the location permissions below are needed. - -```xml - - -``` - -#### Health Connect +### Google Health Connect (Android) Health Connect requires the following lines in the `AndroidManifest.xml` file (see also the example app): @@ -81,17 +65,38 @@ In the Health Connect permissions activity there is a link to your privacy polic ``` -Health Connect on Android it requires special permissions in the `AndroidManifest.xml` file. The permissions can be found here: +For each data type you want to access, the READ and WRITE permissions need to be added to the `AndroidManifest.xml` file. The list of [permissions](https://developer.android.com/health-and-fitness/guides/health-connect/plan/data-types#permissions) can be found here on the [data types](https://developer.android.com/health-and-fitness/guides/health-connect/plan/data-types) page. -Example shown here (can also be found in the example app): +An example of asking for permission to read and write heart rate data is shown below and more examples can also be found in the example app. ```xml -... ``` -Furthermore, an `intent-filter` needs to be added to the `.MainActivity` activity. +Accessing fitness data (e.g. Steps) requires permission to access the "Activity Recognition" API. To set it add the following line to your `AndroidManifest.xml` file. + +```xml + +``` + +Additionally, for workouts, if the distance of a workout is requested then the location permissions below are needed. + +```xml + + +``` + +Because this is labeled as a `dangerous` protection level, the permission system will not grant it automatically and it requires the user's action. +You can prompt the user for it using the [permission_handler](https://pub.dev/packages/permission_handler) plugin. +Follow the plugin setup instructions and add the following line before requesting the data: + +```dart +await Permission.activityRecognition.request(); +await Permission.location.request(); +``` + +Finally, an `intent-filter` needs to be added to the `.MainActivity` activity. ```xml ( + "uuid" to record.metadata.id, "workoutActivityType" to (workoutTypeMap .filterValues { @@ -792,8 +794,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : convertRecordStage( recStage, dataType, - rec.metadata.dataOrigin - .packageName + rec.metadata ) ) } @@ -824,10 +825,13 @@ class HealthPlugin(private var channel: MethodChannel? = null) : private fun convertRecordStage( stage: SleepSessionRecord.Stage, dataType: String, - sourceName: String + metadata: Metadata ): List> { + var sourceName = metadata.dataOrigin + .packageName return listOf( mapOf( + "uuid" to metadata.id, "stage" to stage.stage, "value" to ChronoUnit.MINUTES.between( @@ -923,6 +927,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is WeightRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.weight .inKilograms, @@ -942,6 +948,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is HeightRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.height .inMeters, @@ -961,6 +969,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is BodyFatRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.percentage .value, @@ -980,6 +990,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is StepsRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.count, "date_from" to record.startTime @@ -997,6 +1009,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is ActiveCaloriesBurnedRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.energy .inKilocalories, @@ -1016,6 +1030,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is HeartRateRecord -> return record.samples.map { mapOf( + "uuid" to + metadata.id, "value" to it.beatsPerMinute, "date_from" to it.time.toEpochMilli(), @@ -1030,6 +1046,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is HeartRateVariabilityRmssdRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.heartRateVariabilityMillis, "date_from" to @@ -1048,6 +1066,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is BodyTemperatureRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.temperature .inCelsius, @@ -1067,6 +1087,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is BodyWaterMassRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.mass .inKilograms, @@ -1086,6 +1108,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is BloodPressureRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to if (dataType == BLOOD_PRESSURE_DIASTOLIC @@ -1111,6 +1135,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is OxygenSaturationRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.percentage .value, @@ -1130,6 +1156,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is BloodGlucoseRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.level .inMilligramsPerDeciliter, @@ -1149,6 +1177,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is DistanceRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.distance .inMeters, @@ -1168,6 +1198,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is HydrationRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.volume .inLiters, @@ -1187,6 +1219,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is TotalCaloriesBurnedRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.energy .inKilocalories, @@ -1206,6 +1240,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is BasalMetabolicRateRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.basalMetabolicRate .inKilocaloriesPerDay, @@ -1225,6 +1261,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is SleepSessionRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "date_from" to record.startTime .toEpochMilli(), @@ -1247,6 +1285,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is RestingHeartRateRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.beatsPerMinute, "date_from" to @@ -1265,6 +1305,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is FloorsClimbedRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.floors, "date_from" to record.startTime @@ -1282,6 +1324,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is RespiratoryRateRecord -> return listOf( mapOf( + "uuid" to + metadata.id, "value" to record.rate, "date_from" to record.time @@ -1299,6 +1343,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is NutritionRecord -> return listOf( mapOf( + "uuid" to metadata.id, "calories" to record.energy?.inKilocalories, "protein" to record.protein?.inGrams, "carbs" to record.totalCarbohydrate?.inGrams, @@ -1362,6 +1407,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : is MenstruationFlowRecord -> return listOf( mapOf( + "uuid" to metadata.id, "value" to record.flow, "date_from" to record.time.toEpochMilli(), "date_to" to record.time.toEpochMilli(), @@ -2180,10 +2226,13 @@ class HealthPlugin(private var channel: MethodChannel? = null) : "CALISTHENICS" to ExerciseSessionRecord .EXERCISE_TYPE_CALISTHENICS, + "CARDIO_DANCE" to + ExerciseSessionRecord + .EXERCISE_TYPE_DANCING, "CRICKET" to ExerciseSessionRecord.EXERCISE_TYPE_CRICKET, - // "CROSS_COUNTRY_SKIING" to ExerciseSessionRecord.EXERCISE_TYPE_SKIING_CROSS_COUNTRY, + "CROSS_COUNTRY_SKIING" to ExerciseSessionRecord.EXERCISE_TYPE_SKIING, "DANCING" to ExerciseSessionRecord.EXERCISE_TYPE_DANCING, - // "DOWNHILL_SKIING" to ExerciseSessionRecord.EXERCISE_TYPE_SKIING_DOWNHILL, + "DOWNHILL_SKIING" to ExerciseSessionRecord.EXERCISE_TYPE_SKIING, "ELLIPTICAL" to ExerciseSessionRecord .EXERCISE_TYPE_ELLIPTICAL, @@ -2242,6 +2291,9 @@ class HealthPlugin(private var channel: MethodChannel? = null) : ExerciseSessionRecord .EXERCISE_TYPE_SNOWSHOEING, // "SOCCER" to ExerciseSessionRecord.EXERCISE_TYPE_FOOTBALL_SOCCER, + "SOCIAL_DANCE" to + ExerciseSessionRecord + .EXERCISE_TYPE_DANCING, "SOFTBALL" to ExerciseSessionRecord.EXERCISE_TYPE_SOFTBALL, "SQUASH" to ExerciseSessionRecord.EXERCISE_TYPE_SQUASH, "STAIR_CLIMBING_MACHINE" to @@ -2277,6 +2329,12 @@ class HealthPlugin(private var channel: MethodChannel? = null) : "WHEELCHAIR" to ExerciseSessionRecord .EXERCISE_TYPE_WHEELCHAIR, + "WHEELCHAIR_RUN_PACE" to + ExerciseSessionRecord + .EXERCISE_TYPE_WHEELCHAIR, + "WHEELCHAIR_WALK_PACE" to + ExerciseSessionRecord + .EXERCISE_TYPE_WHEELCHAIR, "YOGA" to ExerciseSessionRecord.EXERCISE_TYPE_YOGA, "OTHER" to ExerciseSessionRecord.EXERCISE_TYPE_OTHER_WORKOUT, ) diff --git a/packages/health/example/lib/main.dart b/packages/health/example/lib/main.dart index 316222413..3912bda41 100644 --- a/packages/health/example/lib/main.dart +++ b/packages/health/example/lib/main.dart @@ -217,11 +217,6 @@ class _HealthAppState extends State { type: HealthDataType.HEART_RATE, startTime: earlier, endTime: now); - success &= await Health().writeHealthData( - value: 30, - type: HealthDataType.HEART_RATE_VARIABILITY_RMSSD, - startTime: earlier, - endTime: now); success &= await Health().writeHealthData( value: 37, type: HealthDataType.BODY_TEMPERATURE, diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift index 9b00a3c2b..19d8cc207 100644 --- a/packages/health/ios/Classes/SwiftHealthPlugin.swift +++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift @@ -1339,8 +1339,10 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { workoutActivityTypeMap["PADDLE_SPORTS"] = .paddleSports workoutActivityTypeMap["ROWING"] = .rowing workoutActivityTypeMap["SAILING"] = .sailing - workoutActivityTypeMap["SURFING_SPORTS"] = .surfingSports + workoutActivityTypeMap["SURFING"] = .surfingSports workoutActivityTypeMap["SWIMMING"] = .swimming + workoutActivityTypeMap["SWIMMING_OPEN_WATER"] = .swimming + workoutActivityTypeMap["SWIMMING_POOL"] = .swimming workoutActivityTypeMap["WATER_FITNESS"] = .waterFitness workoutActivityTypeMap["WATER_POLO"] = .waterPolo workoutActivityTypeMap["WATER_SPORTS"] = .waterSports diff --git a/packages/health/lib/health.g.dart b/packages/health/lib/health.g.dart index 89ded9d4c..13f14c5a6 100644 --- a/packages/health/lib/health.g.dart +++ b/packages/health/lib/health.g.dart @@ -8,6 +8,7 @@ part of 'health.dart'; HealthDataPoint _$HealthDataPointFromJson(Map json) => HealthDataPoint( + uuid: json['uuid'] as String, value: HealthValue.fromJson(json['value'] as Map), type: $enumDecode(_$HealthDataTypeEnumMap, json['type']), unit: $enumDecode(_$HealthDataUnitEnumMap, json['unit']), @@ -28,6 +29,7 @@ HealthDataPoint _$HealthDataPointFromJson(Map json) => Map _$HealthDataPointToJson(HealthDataPoint instance) { final val = { + 'uuid': instance.uuid, 'value': instance.value, 'type': _$HealthDataTypeEnumMap[instance.type]!, 'unit': _$HealthDataUnitEnumMap[instance.unit]!, @@ -318,103 +320,103 @@ const _$HealthWorkoutActivityTypeEnumMap = { HealthWorkoutActivityType.ARCHERY: 'ARCHERY', HealthWorkoutActivityType.AUSTRALIAN_FOOTBALL: 'AUSTRALIAN_FOOTBALL', HealthWorkoutActivityType.BADMINTON: 'BADMINTON', - HealthWorkoutActivityType.BARRE: 'BARRE', HealthWorkoutActivityType.BASEBALL: 'BASEBALL', HealthWorkoutActivityType.BASKETBALL: 'BASKETBALL', - HealthWorkoutActivityType.BIKING_STATIONARY: 'BIKING_STATIONARY', HealthWorkoutActivityType.BIKING: 'BIKING', - HealthWorkoutActivityType.BOWLING: 'BOWLING', HealthWorkoutActivityType.BOXING: 'BOXING', - HealthWorkoutActivityType.CALISTHENICS: 'CALISTHENICS', - HealthWorkoutActivityType.CARDIO_DANCE: 'CARDIO_DANCE', - HealthWorkoutActivityType.CLIMBING: 'CLIMBING', - HealthWorkoutActivityType.COOLDOWN: 'COOLDOWN', - HealthWorkoutActivityType.CORE_TRAINING: 'CORE_TRAINING', HealthWorkoutActivityType.CRICKET: 'CRICKET', HealthWorkoutActivityType.CROSS_COUNTRY_SKIING: 'CROSS_COUNTRY_SKIING', - HealthWorkoutActivityType.CROSS_TRAINING: 'CROSS_TRAINING', HealthWorkoutActivityType.CURLING: 'CURLING', - HealthWorkoutActivityType.DANCING: 'DANCING', - HealthWorkoutActivityType.DISC_SPORTS: 'DISC_SPORTS', HealthWorkoutActivityType.DOWNHILL_SKIING: 'DOWNHILL_SKIING', HealthWorkoutActivityType.ELLIPTICAL: 'ELLIPTICAL', - HealthWorkoutActivityType.EQUESTRIAN_SPORTS: 'EQUESTRIAN_SPORTS', HealthWorkoutActivityType.FENCING: 'FENCING', - HealthWorkoutActivityType.FISHING: 'FISHING', - HealthWorkoutActivityType.FITNESS_GAMING: 'FITNESS_GAMING', - HealthWorkoutActivityType.FLEXIBILITY: 'FLEXIBILITY', - HealthWorkoutActivityType.FRISBEE_DISC: 'FRISBEE_DISC', - HealthWorkoutActivityType.FUNCTIONAL_STRENGTH_TRAINING: - 'FUNCTIONAL_STRENGTH_TRAINING', HealthWorkoutActivityType.GOLF: 'GOLF', - HealthWorkoutActivityType.GUIDED_BREATHING: 'GUIDED_BREATHING', HealthWorkoutActivityType.GYMNASTICS: 'GYMNASTICS', - HealthWorkoutActivityType.HAND_CYCLING: 'HAND_CYCLING', HealthWorkoutActivityType.HANDBALL: 'HANDBALL', HealthWorkoutActivityType.HIGH_INTENSITY_INTERVAL_TRAINING: 'HIGH_INTENSITY_INTERVAL_TRAINING', HealthWorkoutActivityType.HIKING: 'HIKING', HealthWorkoutActivityType.HOCKEY: 'HOCKEY', - HealthWorkoutActivityType.HUNTING: 'HUNTING', - HealthWorkoutActivityType.ICE_SKATING: 'ICE_SKATING', HealthWorkoutActivityType.JUMP_ROPE: 'JUMP_ROPE', HealthWorkoutActivityType.KICKBOXING: 'KICKBOXING', - HealthWorkoutActivityType.LACROSSE: 'LACROSSE', HealthWorkoutActivityType.MARTIAL_ARTS: 'MARTIAL_ARTS', - HealthWorkoutActivityType.MIND_AND_BODY: 'MIND_AND_BODY', - HealthWorkoutActivityType.MIXED_CARDIO: 'MIXED_CARDIO', - HealthWorkoutActivityType.PADDLE_SPORTS: 'PADDLE_SPORTS', - HealthWorkoutActivityType.PARAGLIDING: 'PARAGLIDING', - HealthWorkoutActivityType.PICKLEBALL: 'PICKLEBALL', HealthWorkoutActivityType.PILATES: 'PILATES', - HealthWorkoutActivityType.PLAY: 'PLAY', - HealthWorkoutActivityType.PREPARATION_AND_RECOVERY: - 'PREPARATION_AND_RECOVERY', HealthWorkoutActivityType.RACQUETBALL: 'RACQUETBALL', - HealthWorkoutActivityType.ROCK_CLIMBING: 'ROCK_CLIMBING', - HealthWorkoutActivityType.ROWING_MACHINE: 'ROWING_MACHINE', HealthWorkoutActivityType.ROWING: 'ROWING', HealthWorkoutActivityType.RUGBY: 'RUGBY', - HealthWorkoutActivityType.RUNNING_TREADMILL: 'RUNNING_TREADMILL', HealthWorkoutActivityType.RUNNING: 'RUNNING', HealthWorkoutActivityType.SAILING: 'SAILING', - HealthWorkoutActivityType.SCUBA_DIVING: 'SCUBA_DIVING', HealthWorkoutActivityType.SKATING: 'SKATING', - HealthWorkoutActivityType.SKIING: 'SKIING', - HealthWorkoutActivityType.SNOW_SPORTS: 'SNOW_SPORTS', HealthWorkoutActivityType.SNOWBOARDING: 'SNOWBOARDING', - HealthWorkoutActivityType.SNOWSHOEING: 'SNOWSHOEING', HealthWorkoutActivityType.SOCCER: 'SOCCER', - HealthWorkoutActivityType.SOCIAL_DANCE: 'SOCIAL_DANCE', HealthWorkoutActivityType.SOFTBALL: 'SOFTBALL', HealthWorkoutActivityType.SQUASH: 'SQUASH', - HealthWorkoutActivityType.STAIR_CLIMBING_MACHINE: 'STAIR_CLIMBING_MACHINE', HealthWorkoutActivityType.STAIR_CLIMBING: 'STAIR_CLIMBING', + HealthWorkoutActivityType.SWIMMING: 'SWIMMING', + HealthWorkoutActivityType.TABLE_TENNIS: 'TABLE_TENNIS', + HealthWorkoutActivityType.TENNIS: 'TENNIS', + HealthWorkoutActivityType.VOLLEYBALL: 'VOLLEYBALL', + HealthWorkoutActivityType.WALKING: 'WALKING', + HealthWorkoutActivityType.WATER_POLO: 'WATER_POLO', + HealthWorkoutActivityType.YOGA: 'YOGA', + HealthWorkoutActivityType.BARRE: 'BARRE', + HealthWorkoutActivityType.BOWLING: 'BOWLING', + HealthWorkoutActivityType.CARDIO_DANCE: 'CARDIO_DANCE', + HealthWorkoutActivityType.CLIMBING: 'CLIMBING', + HealthWorkoutActivityType.COOLDOWN: 'COOLDOWN', + HealthWorkoutActivityType.CORE_TRAINING: 'CORE_TRAINING', + HealthWorkoutActivityType.CROSS_TRAINING: 'CROSS_TRAINING', + HealthWorkoutActivityType.DISC_SPORTS: 'DISC_SPORTS', + HealthWorkoutActivityType.EQUESTRIAN_SPORTS: 'EQUESTRIAN_SPORTS', + HealthWorkoutActivityType.FISHING: 'FISHING', + HealthWorkoutActivityType.FITNESS_GAMING: 'FITNESS_GAMING', + HealthWorkoutActivityType.FLEXIBILITY: 'FLEXIBILITY', + HealthWorkoutActivityType.FUNCTIONAL_STRENGTH_TRAINING: + 'FUNCTIONAL_STRENGTH_TRAINING', + HealthWorkoutActivityType.HAND_CYCLING: 'HAND_CYCLING', + HealthWorkoutActivityType.HUNTING: 'HUNTING', + HealthWorkoutActivityType.LACROSSE: 'LACROSSE', + HealthWorkoutActivityType.MIND_AND_BODY: 'MIND_AND_BODY', + HealthWorkoutActivityType.MIXED_CARDIO: 'MIXED_CARDIO', + HealthWorkoutActivityType.PADDLE_SPORTS: 'PADDLE_SPORTS', + HealthWorkoutActivityType.PICKLEBALL: 'PICKLEBALL', + HealthWorkoutActivityType.PLAY: 'PLAY', + HealthWorkoutActivityType.PREPARATION_AND_RECOVERY: + 'PREPARATION_AND_RECOVERY', + HealthWorkoutActivityType.SNOW_SPORTS: 'SNOW_SPORTS', + HealthWorkoutActivityType.SOCIAL_DANCE: 'SOCIAL_DANCE', HealthWorkoutActivityType.STAIRS: 'STAIRS', HealthWorkoutActivityType.STEP_TRAINING: 'STEP_TRAINING', - HealthWorkoutActivityType.STRENGTH_TRAINING: 'STRENGTH_TRAINING', - HealthWorkoutActivityType.SURFING_SPORTS: 'SURFING_SPORTS', HealthWorkoutActivityType.SURFING: 'SURFING', - HealthWorkoutActivityType.SWIMMING_OPEN_WATER: 'SWIMMING_OPEN_WATER', - HealthWorkoutActivityType.SWIMMING_POOL: 'SWIMMING_POOL', - HealthWorkoutActivityType.SWIMMING: 'SWIMMING', - HealthWorkoutActivityType.TABLE_TENNIS: 'TABLE_TENNIS', HealthWorkoutActivityType.TAI_CHI: 'TAI_CHI', - HealthWorkoutActivityType.TENNIS: 'TENNIS', HealthWorkoutActivityType.TRACK_AND_FIELD: 'TRACK_AND_FIELD', HealthWorkoutActivityType.TRADITIONAL_STRENGTH_TRAINING: 'TRADITIONAL_STRENGTH_TRAINING', - HealthWorkoutActivityType.VOLLEYBALL: 'VOLLEYBALL', - HealthWorkoutActivityType.WALKING: 'WALKING', HealthWorkoutActivityType.WATER_FITNESS: 'WATER_FITNESS', - HealthWorkoutActivityType.WATER_POLO: 'WATER_POLO', HealthWorkoutActivityType.WATER_SPORTS: 'WATER_SPORTS', - HealthWorkoutActivityType.WEIGHTLIFTING: 'WEIGHTLIFTING', HealthWorkoutActivityType.WHEELCHAIR_RUN_PACE: 'WHEELCHAIR_RUN_PACE', HealthWorkoutActivityType.WHEELCHAIR_WALK_PACE: 'WHEELCHAIR_WALK_PACE', - HealthWorkoutActivityType.WHEELCHAIR: 'WHEELCHAIR', HealthWorkoutActivityType.WRESTLING: 'WRESTLING', - HealthWorkoutActivityType.YOGA: 'YOGA', + HealthWorkoutActivityType.BIKING_STATIONARY: 'BIKING_STATIONARY', + HealthWorkoutActivityType.CALISTHENICS: 'CALISTHENICS', + HealthWorkoutActivityType.DANCING: 'DANCING', + HealthWorkoutActivityType.FRISBEE_DISC: 'FRISBEE_DISC', + HealthWorkoutActivityType.GUIDED_BREATHING: 'GUIDED_BREATHING', + HealthWorkoutActivityType.ICE_SKATING: 'ICE_SKATING', + HealthWorkoutActivityType.PARAGLIDING: 'PARAGLIDING', + HealthWorkoutActivityType.ROCK_CLIMBING: 'ROCK_CLIMBING', + HealthWorkoutActivityType.ROWING_MACHINE: 'ROWING_MACHINE', + HealthWorkoutActivityType.RUNNING_TREADMILL: 'RUNNING_TREADMILL', + HealthWorkoutActivityType.SCUBA_DIVING: 'SCUBA_DIVING', + HealthWorkoutActivityType.SKIING: 'SKIING', + HealthWorkoutActivityType.SNOWSHOEING: 'SNOWSHOEING', + HealthWorkoutActivityType.STAIR_CLIMBING_MACHINE: 'STAIR_CLIMBING_MACHINE', + HealthWorkoutActivityType.STRENGTH_TRAINING: 'STRENGTH_TRAINING', + HealthWorkoutActivityType.SWIMMING_OPEN_WATER: 'SWIMMING_OPEN_WATER', + HealthWorkoutActivityType.SWIMMING_POOL: 'SWIMMING_POOL', + HealthWorkoutActivityType.WALKING_TREADMILL: 'WALKING_TREADMILL', + HealthWorkoutActivityType.WEIGHTLIFTING: 'WEIGHTLIFTING', + HealthWorkoutActivityType.WHEELCHAIR: 'WHEELCHAIR', HealthWorkoutActivityType.OTHER: 'OTHER', }; diff --git a/packages/health/lib/src/health_data_point.dart b/packages/health/lib/src/health_data_point.dart index b98734c5a..ec5a98a9c 100644 --- a/packages/health/lib/src/health_data_point.dart +++ b/packages/health/lib/src/health_data_point.dart @@ -8,6 +8,9 @@ enum HealthPlatformType { appleHealth, googleHealthConnect } /// as value. @JsonSerializable(fieldRename: FieldRename.snake, includeIfNull: false) class HealthDataPoint { + /// UUID of the data point. + String uuid; + /// The quantity value of the data point HealthValue value; @@ -51,6 +54,7 @@ class HealthDataPoint { Map? metadata; HealthDataPoint({ + required this.uuid, required this.value, required this.type, required this.unit, @@ -129,6 +133,7 @@ class HealthDataPoint { ? null : Map.from(dataPoint['metadata'] as Map); final unit = dataTypeToUnit[dataType] ?? HealthDataUnit.UNKNOWN_UNIT; + final String? uuid = dataPoint["uuid"] as String?; // Set WorkoutSummary, if available. WorkoutSummary? workoutSummary; @@ -140,6 +145,7 @@ class HealthDataPoint { } return HealthDataPoint( + uuid: uuid ?? "", value: value, type: dataType, unit: unit, @@ -157,6 +163,7 @@ class HealthDataPoint { @override String toString() => """$runtimeType - + uuid: $uuid, value: ${value.toString()}, unit: ${unit.name}, dateFrom: $dateFrom, @@ -173,6 +180,7 @@ class HealthDataPoint { @override bool operator ==(Object other) => other is HealthDataPoint && + uuid == other.uuid && value == other.value && unit == other.unit && dateFrom == other.dateFrom && @@ -186,6 +194,6 @@ class HealthDataPoint { metadata == other.metadata; @override - int get hashCode => Object.hash(value, unit, dateFrom, dateTo, type, + int get hashCode => Object.hash(uuid, value, unit, dateFrom, dateTo, type, sourcePlatform, sourceDeviceId, sourceId, sourceName, metadata); } diff --git a/packages/health/lib/src/health_plugin.dart b/packages/health/lib/src/health_plugin.dart index 87de5bcba..9375e5c81 100644 --- a/packages/health/lib/src/health_plugin.dart +++ b/packages/health/lib/src/health_plugin.dart @@ -265,6 +265,7 @@ class Health { (weights[i].value as NumericHealthValue).numericValue.toDouble() / (h * h); final x = HealthDataPoint( + uuid: '', value: NumericHealthValue(numericValue: bmiValue), type: dataType, unit: unit, @@ -310,6 +311,10 @@ class Health { throw ArgumentError( "Adding workouts should be done using the writeWorkoutData method."); } + // If not implemented on platform, throw an exception + if (!isDataTypeAvailable(type)) { + throw HealthException(type, 'Not available on platform $platformType'); + } endTime ??= startTime; if (startTime.isAfter(endTime)) { throw ArgumentError("startTime must be equal or earlier than endTime"); @@ -1119,7 +1124,7 @@ class Health { HealthWorkoutActivityType.STAIR_CLIMBING, HealthWorkoutActivityType.STAIRS, HealthWorkoutActivityType.STEP_TRAINING, - HealthWorkoutActivityType.SURFING_SPORTS, + HealthWorkoutActivityType.SURFING, HealthWorkoutActivityType.SWIMMING, HealthWorkoutActivityType.TABLE_TENNIS, HealthWorkoutActivityType.TAI_CHI, @@ -1135,6 +1140,8 @@ class Health { HealthWorkoutActivityType.WHEELCHAIR_WALK_PACE, HealthWorkoutActivityType.WRESTLING, HealthWorkoutActivityType.YOGA, + HealthWorkoutActivityType.SWIMMING_OPEN_WATER, + HealthWorkoutActivityType.SWIMMING_POOL, }.contains(type); } @@ -1151,6 +1158,7 @@ class Health { HealthWorkoutActivityType.BASKETBALL, HealthWorkoutActivityType.BIKING, HealthWorkoutActivityType.BOXING, + HealthWorkoutActivityType.CARDIO_DANCE, HealthWorkoutActivityType.CRICKET, HealthWorkoutActivityType.CROSS_COUNTRY_SKIING, HealthWorkoutActivityType.CURLING, @@ -1173,6 +1181,7 @@ class Health { HealthWorkoutActivityType.SKATING, HealthWorkoutActivityType.SNOWBOARDING, HealthWorkoutActivityType.SOCCER, + HealthWorkoutActivityType.SOCIAL_DANCE, HealthWorkoutActivityType.SOFTBALL, HealthWorkoutActivityType.SQUASH, HealthWorkoutActivityType.STAIR_CLIMBING, @@ -1181,6 +1190,8 @@ class Health { HealthWorkoutActivityType.VOLLEYBALL, HealthWorkoutActivityType.WALKING, HealthWorkoutActivityType.WATER_POLO, + HealthWorkoutActivityType.WHEELCHAIR_RUN_PACE, + HealthWorkoutActivityType.WHEELCHAIR_WALK_PACE, HealthWorkoutActivityType.YOGA, // Android only diff --git a/packages/health/lib/src/health_value_types.dart b/packages/health/lib/src/health_value_types.dart index 6a217ea93..502328b51 100644 --- a/packages/health/lib/src/health_value_types.dart +++ b/packages/health/lib/src/health_value_types.dart @@ -153,7 +153,9 @@ class WorkoutHealthValue extends HealthValue { factory WorkoutHealthValue.fromHealthDataPoint(dynamic dataPoint) => WorkoutHealthValue( workoutActivityType: HealthWorkoutActivityType.values.firstWhere( - (element) => element.name == dataPoint['workoutActivityType']), + (element) => element.name == dataPoint['workoutActivityType'], + orElse: () => HealthWorkoutActivityType.OTHER, + ), totalEnergyBurned: dataPoint['totalEnergyBurned'] != null ? (dataPoint['totalEnergyBurned'] as num).toInt() : null, diff --git a/packages/health/lib/src/heath_data_types.dart b/packages/health/lib/src/heath_data_types.dart index 014cfe1da..c498de3a1 100644 --- a/packages/health/lib/src/heath_data_types.dart +++ b/packages/health/lib/src/heath_data_types.dart @@ -518,7 +518,7 @@ enum HealthWorkoutActivityType { SOCIAL_DANCE, STAIRS, STEP_TRAINING, - SURFING_SPORTS, + SURFING, TAI_CHI, TRACK_AND_FIELD, TRADITIONAL_STRENGTH_TRAINING, @@ -544,7 +544,6 @@ enum HealthWorkoutActivityType { SNOWSHOEING, STAIR_CLIMBING_MACHINE, STRENGTH_TRAINING, - SURFING, SWIMMING_OPEN_WATER, SWIMMING_POOL, WALKING_TREADMILL,