health 13.2.0

SDKflutter
Platformandroidios

Wrapper for Apple's HealthKit on iOS and Google's Health Connect on Android.

Health

Enables reading and writing health data from/to Apple Health and Google Health Connect.

NOTE: Google has deprecated the Google Fit API. According to the documentation, 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:

  • handling permissions to access health data using the hasPermissions, requestAuthorization, revokePermissions methods.
  • reading health data using the getHealthDataFromTypes method.
  • writing health data using the writeHealthData method.
  • writing workouts using the writeWorkout method.
  • writing meals on iOS (Apple Health) & Android using the writeMeal method.
  • writing audiograms on iOS using the writeAudiogram method.
  • writing blood pressure data using the writeBloodPressure method.
  • accessing total step counts using the getTotalStepsInInterval method.
  • cleaning up duplicate data points via the removeDuplicates method.
  • removing data of a given type in a selected period of time using the delete method.

Note that for Android, the target phone needs to have the Health Connect app installed (which is currently in beta) and have access to the internet.

See the tables below for supported health and workout data types.

Setup

Apple Health (iOS)

First, add the following 2 entries to the Info.plist:

<key>NSHealthShareUsageDescription</key>
<string>We will sync your data with the Apple Health app to give you better insights</string>
<key>NSHealthUpdateUsageDescription</key>
<string>We will sync your data with the Apple Health app to give you better insights</string>

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.

Google Health Connect (Android)

Health Connect requires the following lines in the AndroidManifest.xml file (see also the example app):

<!-- Check whether Health Connect is installed or not -->
<queries>
  <package android:name="com.google.android.apps.healthdata" />
  <intent>
    <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
  </intent>
</queries>

In the Health Connect permissions activity there is a link to your privacy policy. You need to grant the Health Connect app access in order to link back to your privacy policy. In the example below, you should either replace .MainActivity with an activity that presents the privacy policy or have the Main Activity route the user to the policy. This step may be required to pass Google app review when requesting access to sensitive permissions.

<activity-alias
     android:name="ViewPermissionUsageActivity"
     android:exported="true"
     android:targetActivity=".MainActivity"
     android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
        <intent-filter>
            <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
            <category android:name="android.intent.category.HEALTH_PERMISSIONS" />
        </intent-filter>
</activity-alias>

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 can be found here on the data types page.

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.

<uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
<uses-permission android:name="android.permission.health.WRITE_HEART_RATE"/>

By default, Health Connect restricts read data to 30 days from when permission has been granted.

You can check and request access to historical data using the isHealthDataHistoryAuthorized and requestHealthDataHistoryAuthorization methods, respectively.

The above methods require the following permission to be declared:

<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>

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.

<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>

Additionally, for workouts, if the distance of a workout is requested then the location permissions below are needed.

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

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 plugin. Follow the plugin setup instructions and add the following line before requesting the data:

await Permission.activityRecognition.request();
await Permission.location.request();

Finally, an intent-filter needs to be added to the .MainActivity activity.

<activity
  android:name=".MainActivity"
  android:exported="true"

  ....

  <!-- Intention to show Permissions screen for Health Connect API -->
  <intent-filter>
    <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
  </intent-filter>
</activity>

There's a debug, main and profile version which are chosen depending on how you start your app. In general, it's sufficient to add permission only to the main version.

Android 14

This plugin uses the new registerForActivityResult when requesting permissions from Health Connect. In order for that to work, the Main app's activity should extend FlutterFragmentActivity instead of FlutterActivity. This adjustment allows casting from Activity to ComponentActivity for accessing registerForActivityResult.

In your MainActivity.kt file, update the MainActivity class so that it extends FlutterFragmentActivity instead of the default FlutterActivity:

...
import io.flutter.embedding.android.FlutterFragmentActivity
...

class MainActivity: FlutterFragmentActivity() {
...
}

Android X

Replace the content of the android/gradle.properties file with the following lines:

org.gradle.jvmargs=-Xmx1536M
android.enableJetifier=true
android.useAndroidX=true

Usage

See the example app for detailed examples of how to use the Health API.

A instance of the Health plugin is create using the Health() constructor and is subsequently configured calling the configure method. Once configured, the plugin can be used for handling permissions and getting and adding data to Apple Health or Google Health Connect. Below is a simplified flow of how to use the plugin.


  // Global Health instance
  final health = Health();

  // configure the health plugin before use.
  await health.configure();


  // define the types to get
  var types = [
    HealthDataType.STEPS,
    HealthDataType.BLOOD_GLUCOSE,
  ];

  // requesting access to the data types before reading them
  bool requested = await health.requestAuthorization(types);

  var now = DateTime.now();

  // fetch health data from the last 24 hours
  List<HealthDataPoint> healthData = await health.getHealthDataFromTypes(
     now.subtract(Duration(days: 1)), now, types);

  // request permissions to write steps and blood glucose
  types = [HealthDataType.STEPS, HealthDataType.BLOOD_GLUCOSE];
  var permissions = [
      HealthDataAccess.READ_WRITE,
      HealthDataAccess.READ_WRITE
  ];
  await health.requestAuthorization(types, permissions: permissions);

  // write steps and blood glucose
  bool success = await health.writeHealthData(10, HealthDataType.STEPS, now, now);
  success = await health.writeHealthData(3.1, HealthDataType.BLOOD_GLUCOSE, now, now);

  // you can also specify the recording method to store in the metadata (default is RecordingMethod.automatic)
  // on iOS only `RecordingMethod.automatic` and `RecordingMethod.manual` are supported
  // Android additionally supports `RecordingMethod.active` and `RecordingMethod.unknown`
  success &= await health.writeHealthData(10, HealthDataType.STEPS, now, now, recordingMethod: RecordingMethod.manual);

  // get the number of steps for today
  var midnight = DateTime(now.year, now.month, now.day);
  int? steps = await health.getTotalStepsInInterval(midnight, now);

Health Data

A HealthDataPoint object contains the following data fields:

String uuid;
HealthValue value;
HealthDataType type;
HealthDataUnit unit;
DateTime dateFrom;
DateTime dateTo;
HealthPlatformType sourcePlatform;
String sourceDeviceId;
String sourceId;
String sourceName;
RecordingMethod recordingMethod;
WorkoutSummary? workoutSummary;

where a HealthValue can be any type of AudiogramHealthValue, ElectrocardiogramHealthValue, ElectrocardiogramVoltageValue, NumericHealthValue, NutritionHealthValue, or WorkoutHealthValue.

A HealthDataPoint object can be serialized to and from JSON using the toJson() and fromJson() methods. JSON serialization is using camel_case notation. Null values are not serialized. For example;

{
  "value": {
    "__type": "NumericHealthValue",
    "numeric_value": 141.0
  },
  "type": "STEPS",
  "unit": "COUNT",
  "date_from": "2024-04-03T10:06:57.736",
  "date_to": "2024-04-03T10:12:51.724",
  "source_platform": "appleHealth",
  "source_device_id": "F74938B9-C011-4DE4-AA5E-CF41B60B96E7",
  "source_id": "com.apple.health.81AE7156-EC05-47E3-AC93-2D6F65C717DF",
  "source_name": "iPhone12.bardram.net",
  "recording_method": 3
  "value": {
    "__type": "NumericHealthValue",
    "numeric_value": 141.0
  },
  "type": "STEPS",
  "unit": "COUNT",
  "date_from": "2024-04-03T10:06:57.736",
  "date_to": "2024-04-03T10:12:51.724",
  "source_platform": "appleHealth",
  "source_device_id": "F74938B9-C011-4DE4-AA5E-CF41B60B96E7",
  "source_id": "com.apple.health.81AE7156-EC05-47E3-AC93-2D6F65C717DF",
  "source_name": "iPhone12.bardram.net",
  "recording_method": 2
}

Fetch health data

See the example app for a showcasing of how it's done.

Note On iOS the device must be unlocked before health data can be requested. Otherwise an error will be thrown:

flutter: Health Plugin Error:
flutter:  PlatformException(FlutterHealth, Results are null, Optional(Error Domain=com.apple.healthkit Code=6 "Protected health data is inaccessible" UserInfo={NSLocalizedDescription=Protected health data is inaccessible}))

Fetch single health data by UUID

In order to retrieve a single record, it is required to provide String uuid and HealthDataType type.

Please see example below:

HealthDataPoint? healthPoint = await health.getHealthDataByUUID(
  uuid: 'random-uuid-string',
  type: HealthDataType.STEPS,
);
I/FLUTTER_HEALTH( 9161): Success: {uuid=random-uuid-string, value=12, date_from=1742259061009, date_to=1742259092888, source_id=, source_name=com.google.android.apps.fitness, recording_method=0}

Assuming that the uuid and type are coming from your database.

Filtering by recording method

Google Health Connect and Apple HealthKit both provide ways to distinguish samples collected "automatically" and manually entered data by the user.

As such, when fetching data you have the option to filter the fetched data by recording method as such:

List<HealthDataPoint> healthData = await health.getHealthDataFromTypes(
  types: types,
  startTime: yesterday,
  endTime: now,
  recordingMethodsToFilter: [RecordingMethod.manual, RecordingMethod.unknown],
);

Note that for this to work, the information needs to have been provided when writing the data to Health Connect or Apple Health. For example, steps added manually through the Apple Health App will set HKWasUserEntered to true (corresponding to RecordingMethod.manual), however it seems that adding steps manually to Google Fit does not write the data with the RecordingMethod.manual in the metadata, instead it shows up as RecordingMethod.unknown. This is an open issue, and as such filtering manual entries when querying step count on Android with getTotalStepsInInterval(includeManualEntries: false) does not necessarily filter out manual steps.

NOTE: On iOS, you can only filter by RecordingMethod.automatic and RecordingMethod.manual as it is stored HKMetadataKeyWasUserEntered is a boolean value in the metadata.

Filtering out duplicates

If the same data is requested multiple times and saved in the same array duplicates will occur.

A single data point can be compared to each other with the == operator, i.e.

HealthDataPoint p1 = ...;
HealthDataPoint p2 = ...;
bool same = p1 == p2;

If you have a list of data points, duplicates can be removed with:

List<HealthDataPoint> points = ...;
points = health.removeDuplicates(points);

Android: Reading Health Data in Background

Currently health connect allows apps to read health data in the background. In order to achieve this add the following permission to your AndroidManifest.XML:

<!-- For reading data in background -->
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND"/>

Furthermore, the plugin now exposes three new functions to help you check and request access to read data in the background:

  1. isHealthDataInBackgroundAvailable(): Checks if the Health Data in Background feature is available
  2. isHealthDataInBackgroundAuthorized(): Checks the current status of the Health Data in Background permission
  3. requestHealthDataInBackgroundAuthorization(): Requests the Health Data in Background permission.

Fetch single health data by UUID

In order to retrieve a single record, it is required to provide String uuid and HealthDataType type.

Please see example below:

HealthDataPoint? healthPoint = await health.getHealthDataByUUID(
  uuid: 'E9F2EEAD-8FC5-4CE5-9FF5-7C4E535FB8B8',
  type: HealthDataType.WORKOUT,
);
data by UUID: HealthDataPoint -
    uuid: E9F2EEAD-8FC5-4CE5-9FF5-7C4E535FB8B8,
    value: WorkoutHealthValue - workoutActivityType: RUNNING,
           totalEnergyBurned: null,
           totalEnergyBurnedUnit: KILOCALORIE,
           totalDistance: 2400,
           totalDistanceUnit: METER
           totalSteps: null,
           totalStepsUnit: null,
    unit: NO_UNIT,
    dateFrom: 2025-05-02 07:31:00.000,
    dateTo: 2025-05-02 08:25:00.000,
    dataType: WORKOUT,
    platform: HealthPlatformType.appleHealth,
    deviceId: unknown,
    sourceId: com.apple.Health,
    sourceName: Health
    recordingMethod: RecordingMethod.manual
    workoutSummary: WorkoutSummary - workoutType: runningtotalDistance: 2400, totalEnergyBurned: 0, totalSteps: 0
    metadata: null
    deviceModel: null

Assuming that the uuid and type are coming from your database.

Data Types

The plugin supports the following HealthDataType.

Data TypeUnitApple HealthGoogle Health ConnectComments
ACTIVE_ENERGY_BURNEDCALORIESyesyes
ATRIAL_FIBRILLATION_BURDENPERCENTAGEyes
BASAL_ENERGY_BURNEDCALORIESyesyes
BLOOD_GLUCOSEMILLIGRAM_PER_DECILITERyesyes
BLOOD_OXYGENPERCENTAGEyesyes
BLOOD_PRESSURE_DIASTOLICMILLIMETER_OF_MERCURYyesyes
BLOOD_PRESSURE_SYSTOLICMILLIMETER_OF_MERCURYyesyes
BODY_FAT_PERCENTAGEPERCENTAGEyesyes
BODY_MASS_INDEXNO_UNITyesyes
BODY_TEMPERATUREDEGREE_CELSIUSyesyes
BODY_WATER_MASSKILOGRAMSyes
ELECTRODERMAL_ACTIVITYSIEMENSyes
HEART_RATEBEATS_PER_MINUTEyesyes
HEIGHTMETERSyesyes
RESTING_HEART_RATEBEATS_PER_MINUTEyesyes
RESPIRATORY_RATERESPIRATIONS_PER_MINUTEyesyes
PERIPHERAL_PERFUSION_INDEXPERCENTAGEyes
STEPSCOUNTyesyes
WAIST_CIRCUMFERENCEMETERSyes
WALKING_HEART_RATEBEATS_PER_MINUTEyes
WEIGHTKILOGRAMSyesyes
DISTANCE_WALKING_RUNNINGMETERSyes
FLIGHTS_CLIMBEDCOUNTyesyes
DISTANCE_DELTAMETERSyes
MINDFULNESSMINUTESyes
SLEEP_ASLEEPMINUTESyesyeson iOS, this refers to asleepUnspecified, and on Android this refers to STAGE_TYPE_SLEEPING (asleep but specific stage is unknown)
SLEEP_AWAKEMINUTESyesyes
SLEEP_AWAKE_IN_BEDMINUTESyes
SLEEP_DEEPMINUTESyesyes
SLEEP_IN_BEDMINUTESyes
SLEEP_LIGHTMINUTESyesyeson iOS, this refers to asleepCore
SLEEP_OUT_OF_BEDMINUTESyes
SLEEP_REMMINUTESyesyes
SLEEP_UNKNOWNMINUTESyes
SLEEP_SESSIONMINUTESyes
WATERLITERyesyes
EXERCISE_TIMEMINUTESyes
WORKOUTNO_UNITyesyesSee table below
HIGH_HEART_RATE_EVENTNO_UNITyesRequires Apple Watch to write the data
LOW_HEART_RATE_EVENTNO_UNITyesRequires Apple Watch to write the data
IRREGULAR_HEART_RATE_EVENTNO_UNITyesRequires Apple Watch to write the data
HEART_RATE_VARIABILITY_RMSSDMILLISECONDSyes
HEART_RATE_VARIABILITY_SDNNMILLISECONDSyesRequires Apple Watch to write the data
HEADACHE_NOT_PRESENTMINUTESyes
HEADACHE_MILDMINUTESyes
HEADACHE_MODERATEMINUTESyes
HEADACHE_SEVEREMINUTESyes
HEADACHE_UNSPECIFIEDMINUTESyes
AUDIOGRAMDECIBEL_HEARING_LEVELyes
ELECTROCARDIOGRAMVOLTyesRequires Apple Watch to write the data
NUTRITIONNO_UNITyesyes
INSULIN_DELIVERYINTERNATIONAL_UNITyes
MENSTRUATION_FLOWNO_UNITyesyes
WATER_TEMPERATUREDEGREE_CELSIUSyesRelated to/Requires Apple Watch Ultra's Underwater Diving Workout
UNDERWATER_DEPTHMETERyesRelated to/Requires Apple Watch Ultra's Underwater Diving Workout
UV_INDEXCOUNTyes
LEAN_BODY_MASSKILOGRAMSyesyes
WALKING_SPEEDMETER_PER_SECONDyes(yes)On Android this will be recorded as SPEED with similar unit
APPLE_MOVE_TIMESECONDyesREAD Only
APPLE_STAND_HOURHOURyesREAD Only
APPLE_MOVE_TIMESECONDyesREAD Only

Workout Types

The plugin supports the following HealthWorkoutActivityType.

Workout TypeApple HealthGoogle Health ConnectComments
AMERICAN_FOOTBALLyesyes
ARCHERYyes
AUSTRALIAN_FOOTBALLyesyes
BADMINTONyesyes
BARREyes
BASEBALLyesyes
BASKETBALLyesyes
BIKINGyesyeson iOS this is CYCLING, but name changed here to fit with Android
BOWLINGyes
BOXINGyesyes
CALISTHENICSyes
CARDIO_DANCEyes(yes)on Android this will be stored as DANCING
CLIMBINGyes
COOLDOWNyes
CORE_TRAININGyes
CRICKETyesyes
CROSS_COUNTRY_SKIINGyes(yes)on Android this will be stored as SKIING
CROSS_TRAININGyes
CURLINGyes
DANCINGyesyeson iOS this is DANCE, but name changed here to fit with Android
DISC_SPORTSyes
DOWNHILL_SKIINGyes(yes)on Android this will be stored as SKIING
ELLIPTICALyesyes
EQUESTRIAN_SPORTSyes
FENCINGyesyes
FISHINGyes
FITNESS_GAMINGyes
FLEXIBILITYyes
FRISBEE_DISCyes
FUNCTIONAL_STRENGTH_TRAININGyes(yes)on Android this will be stored as STRENGTH_TRAINING
GOLFyesyes
GUIDED_BREATHINGyes
GYMNASTICSyesyes
HAND_CYCLINGyes
HANDBALLyesyes
HIGH_INTENSITY_INTERVAL_TRAININGyesyes
HIKINGyesyes
HOCKEYyes
HUNTINGyes
JUMP_ROPEyes
KICKBOXINGyes
LACROSSEyes
MARTIAL_ARTSyesyes
MIND_AND_BODYyes
MIXED_CARDIOyes
PADDLE_SPORTSyes
PARAGLIDINGyes
PICKLEBALLyes
PILATESyesyes
PLAYyes
PREPARATION_AND_RECOVERYyes
RACQUETBALLyesyes
ROCK_CLIMBING(yes)yeson iOS this will be stored as CLIMBING
ROWINGyesyes
RUGBYyesyes
RUNNINGyesyes
RUNNING_TREADMILL(yes)yeson iOS this will be stored as RUNNING
SAILINGyesyes
SCUBA_DIVINGyes
SKATINGyesyesOn iOS this will be stored as SKATING_SPORTS
SKIING(yes)yeson iOS you have to choose between CROSS_COUNTRY_SKIING and DOWNHILL_SKIING
SNOW_SPORTSyes
SNOWBOARDINGyesyes
SOCCERyes
SOCIAL_DANCEyes(yes)on Android this will be stored as DANCING
SOFTBALLyesyes
SQUASHyesyes
STAIR_CLIMBINGyesyes
STAIR_CLIMBING_MACHINEyes
STAIRSyes
STEP_TRAININGyes
STRENGTH_TRAINING(yes)yeson iOS you have to choose between FUNCTIONAL_STRENGTH_TRAINING or TRADITIONAL_STRENGTH_TRAINING
SURFINGyesyeson iOS this is SURFING_SPORTS, but name changed here to fit with Android
SWIMMINGyes(yes)on Android you have to choose between SWIMMING_OPEN_WATER and SWIMMING_POOL
SWIMMING_OPEN_WATER(yes)yeson iOS this will be stored as SWIMMING
SWIMMING_POOL(yes)yeson iOS this will be stored as SWIMMING
TABLE_TENNISyesyes
TAI_CHIyes
TENNISyesyes
TRACK_AND_FIELDyes
TRADITIONAL_STRENGTH_TRAININGyes(yes)on Android this will be stored as STRENGTH_TRAINING
UNDERWATER_DIVINGyes
VOLLEYBALLyesyes
WALKINGyesyes
WATER_FITNESSyes
WATER_POLOyesyes
WATER_SPORTSyes
WEIGHTLIFTINGyes
WHEELCHAIR(yes)yeson iOS you have to choose between WHEELCHAIR_RUN_PACE or WHEELCHAIR_WALK_PACE
WHEELCHAIR_RUN_PACEyes(yes)on Android this will be stored as WHEELCHAIR
WHEELCHAIR_WALK_PACEyes(yes)on Android this will be stored as WHEELCHAIR
WRESTLINGyes
YOGAyesyes
OTHERyesyes

License

This software is copyright (c) the Technical University of Denmark (DTU) and is part of the Copenhagen Research Platform. This software is available 'as-is' under a MIT license.