Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Health] Add menstruation flow data from Health Connect and HealthKit, characteristic data from HealthKit #1008

Merged
merged 12 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/health/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,11 @@ The plugin supports the following [`HealthDataType`](https://pub.dev/documentati
| AUDIOGRAM | DECIBEL_HEARING_LEVEL | yes | | | |
| ELECTROCARDIOGRAM | VOLT | yes | | | Requires Apple Watch to write the data |
| NUTRITION | NO_UNIT | yes | yes | yes | |
| INSULIN_DELIVERY | INTERNATIONAL_UNIT | yes | | | |
| INSULIN_DELIVERY | INTERNATIONAL_UNIT | yes | | | |
| GENDER | NO_UNIT | yes | | | |
| BLOOD_TYPE | NO_UNIT | yes | | | |
| BIRTH_DATE | NO_UNIT | yes | | | |
| MENSTRUATION_FLOW | NO_UNIT | yes | | yes | |

## Workout Types

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
private var BASAL_ENERGY_BURNED = "BASAL_ENERGY_BURNED"
private var FLIGHTS_CLIMBED = "FLIGHTS_CLIMBED"
private var RESPIRATORY_RATE = "RESPIRATORY_RATE"
private var MENSTRUATION_FLOW = "MENSTRUATION_FLOW"

// TODO support unknown?
private var SLEEP_ASLEEP = "SLEEP_ASLEEP"
Expand Down Expand Up @@ -1111,6 +1112,16 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
}
}

/**
* Save menstrual flow data
*/
private fun writeMenstruationFlow(call: MethodCall, result: Result) {
if (useHealthConnectIfAvailable && healthConnectAvailable) {
writeHCData(call, result)
return
}
}

/**
* Save the blood oxygen saturation, in Google Fit with the supplemental flow rate, in
* HealthConnect without
Expand Down Expand Up @@ -2507,6 +2518,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
"writeWorkoutData" -> writeWorkoutData(call, result)
"writeBloodPressure" -> writeBloodPressure(call, result)
"writeBloodOxygen" -> writeBloodOxygen(call, result)
"writeMenstruationFlow" -> writeMenstruationFlow(call, result)
"writeMeal" -> writeMeal(call, result)
"disconnect" -> disconnect(call, result)
else -> result.notImplemented()
Expand Down Expand Up @@ -3464,6 +3476,18 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
.packageName,
)
)
is MenstruationFlowRecord ->
return listOf(
mapOf<String, Any>(
"value" to record.flow,
"date_from" to record.time.toEpochMilli(),
"date_to" to record.time.toEpochMilli(),
"source_id" to "",
"source_name" to
metadata.dataOrigin
.packageName,
)
)
// is ExerciseSessionRecord -> return listOf(mapOf<String, Any>("value" to ,
// "date_from" to ,
// "date_to" to ,
Expand Down Expand Up @@ -3899,6 +3923,11 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
startZoneOffset = null,
endZoneOffset = null,
)
MENSTRUATION_FLOW -> MenstruationFlowRecord(
time = Instant.ofEpochMilli(startTime),
flow = value.toInt(),
zoneOffset = null,
)
BLOOD_PRESSURE_SYSTOLIC ->
throw IllegalArgumentException(
"You must use the [writeBloodPressure] API "
Expand Down Expand Up @@ -4145,7 +4174,8 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
BASAL_ENERGY_BURNED to BasalMetabolicRateRecord::class,
FLIGHTS_CLIMBED to FloorsClimbedRecord::class,
RESPIRATORY_RATE to RespiratoryRateRecord::class,
TOTAL_CALORIES_BURNED to TotalCaloriesBurnedRecord::class
TOTAL_CALORIES_BURNED to TotalCaloriesBurnedRecord::class,
MENSTRUATION_FLOW to MenstruationFlowRecord::class,
// MOVE_MINUTES to TODO: Find alternative?
// TODO: Implement remaining types
// "ActiveCaloriesBurned" to
Expand All @@ -4169,7 +4199,6 @@ class HealthPlugin(private var channel: MethodChannel? = null) :
// "Height" to HeightRecord::class,
// "Hydration" to HydrationRecord::class,
// "LeanBodyMass" to LeanBodyMassRecord::class,
// "MenstruationFlow" to MenstruationFlowRecord::class,
// "MenstruationPeriod" to MenstruationPeriodRecord::class,
// "Nutrition" to NutritionRecord::class,
// "OvulationTest" to OvulationTestRecord::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
<uses-permission android:name="android.permission.health.WRITE_RESPIRATORY_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_NUTRITION"/>
<uses-permission android:name="android.permission.health.READ_NUTRITION"/>
<uses-permission android:name="android.permission.health.READ_MENSTRUATION"/>
<uses-permission android:name="android.permission.health.WRITE_MENSTRUATION"/>

<application android:label="health_example"
android:icon="@mipmap/ic_launcher">
Expand Down
28 changes: 24 additions & 4 deletions packages/health/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,26 @@ class _HealthAppState extends State<HealthApp> {
// ];

// Set up corresponding permissions

// READ only
List<HealthDataAccess> get permissions =>
types.map((e) => HealthDataAccess.READ).toList();
// List<HealthDataAccess> get permissions =>
// types.map((e) => HealthDataAccess.READ).toList();

// Or both READ and WRITE
// List<HealthDataAccess> get permissions =>
// types.map((e) => HealthDataAccess.READ_WRITE).toList();
List<HealthDataAccess> get permissions => types
.map((type) =>
// can only request READ permissions to the following list of types on iOS
[
HealthDataType.WALKING_HEART_RATE,
HealthDataType.ELECTROCARDIOGRAM,
HealthDataType.HIGH_HEART_RATE_EVENT,
HealthDataType.LOW_HEART_RATE_EVENT,
HealthDataType.IRREGULAR_HEART_RATE_EVENT,
HealthDataType.EXERCISE_TIME,
].contains(type)
? HealthDataAccess.READ
: HealthDataAccess.READ_WRITE)
.toList();

void initState() {
// configure the health plugin before use.
Expand Down Expand Up @@ -326,6 +339,13 @@ class _HealthAppState extends State<HealthApp> {
// },
// );

success &= await Health().writeMenstruationFlow(
flow: MenstrualFlow.medium,
isStartOfCycle: true,
startTime: earlier,
endTime: now,
);

setState(() {
_state = success ? AppState.DATA_ADDED : AppState.DATA_NOT_ADDED;
});
Expand Down
5 changes: 5 additions & 0 deletions packages/health/example/lib/util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const List<HealthDataType> dataTypesIOS = [
HealthDataType.WALKING_HEART_RATE,

HealthDataType.NUTRITION,
HealthDataType.GENDER,
HealthDataType.BLOOD_TYPE,
HealthDataType.BIRTH_DATE,
HealthDataType.MENSTRUATION_FLOW,
];

/// List of data types available on Android.
Expand Down Expand Up @@ -89,4 +93,5 @@ const List<HealthDataType> dataTypesAndroid = [
HealthDataType.FLIGHTS_CLIMBED,
HealthDataType.NUTRITION,
HealthDataType.TOTAL_CALORIES_BURNED,
HealthDataType.MENSTRUATION_FLOW,
];
Loading
Loading