Install & configure
Adding the package takes one command. Most of the work is platform configuration — and you only need the platform you actually ship.
Check your toolchain
| Component | Requirement |
|---|---|
| Flutter | ≥ 3.38.0 (the package declares flutter: '>=3.38.0', sdk: ^3.10.0) |
| Android | API 26+ (Android 8.0), Kotlin 2.1.0, Java 17 |
| iOS | iOS 15.0+, Swift 5.9 |
Flutter 3.38.0 is a hard floor
The package declares flutter: '>=3.38.0', so pub get fails on Flutter 3.35.x and older. This is a resolution requirement, not a recommendation.
Flutter 3.38.0 ships Dart 3.10.0, so a single Flutter upgrade satisfies both constraints. Swift 5.9 and Kotlin 2.1 remain compatible with Swift 5.0 and Kotlin 2.0 code, so the native side is a version bump in your build files.
Full details, including why each floor exists, are in Requirements.
Add the package
flutter pub add health_connectorOr declare it directly in pubspec.yaml:
dependencies:
health_connector: ^3.9.4Depend only on the facade. The Android and iOS implementations arrive transitively, which lets the facade coordinate compatible releases — see Packages.
Configure your platform
Android · Health Connect
Declare a permission for every data type you touch.
Health Connect will not grant access to a type your manifest does not name. Add one
<uses-permission>per type and direction toandroid/app/src/main/AndroidManifest.xml:xml<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Data permissions: one per type, per direction --> <uses-permission android:name="android.permission.health.READ_STEPS" /> <uses-permission android:name="android.permission.health.WRITE_STEPS" /> <uses-permission android:name="android.permission.health.READ_WEIGHT" /> <uses-permission android:name="android.permission.health.WRITE_WEIGHT" /> <uses-permission android:name="android.permission.health.READ_HEART_RATE" /> <uses-permission android:name="android.permission.health.WRITE_HEART_RATE" /> <!-- Exercise sessions, and GPS routes on top of them --> <uses-permission android:name="android.permission.health.READ_EXERCISE" /> <uses-permission android:name="android.permission.health.WRITE_EXERCISE" /> <uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTE" /> <uses-permission android:name="android.permission.health.WRITE_EXERCISE_ROUTE" /> <!-- Feature permissions --> <uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND" /> <uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY" /> </manifest>The permission name follows the data type:
HealthDataType.bloodGlucoseneedsREAD_BLOOD_GLUCOSE. Google's Health Connect data types reference lists every one.Declare the permissions-rationale entry points.
Health Connect and the Android settings UI both need a way into your app to show why you want health data — and they use different mechanisms per OS version. You need both, inside
<application>:xml<application> <activity android:name=".MainActivity" android:exported="true" …> <!-- Your existing MAIN/LAUNCHER intent-filter --> <!-- Android 13 and below --> <intent-filter> <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" /> </intent-filter> </activity> <!-- Android 14 and above --> <activity-alias android:name="ViewPermissionUsageActivity" android:exported="true" android:permission="android.permission.START_VIEW_PERMISSION_USAGE" android:targetActivity=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" /> <category android:name="android.intent.category.HEALTH_PERMISSIONS" /> </intent-filter> </activity-alias> </application>Both entries are required, and they are not interchangeable
Putting
ACTION_SHOW_PERMISSIONS_RATIONALEinside theactivity-aliassatisfies neither path. Google Play reviews this flow, so getting it wrong can hold up a release. The example app's manifest is the reference.Make
MainActivityextendFlutterFragmentActivity.Permission requests use the modern
registerForActivityResultAPI, which needs aFragmentActivityhost. Inandroid/app/src/main/kotlin/.../MainActivity.kt:kotlinpackage com.example.yourapp import io.flutter.embedding.android.FlutterFragmentActivity class MainActivity : FlutterFragmentActivity() { // Your existing code }Enable AndroidX in
android/gradle.properties, since Health Connect is built on AndroidX libraries:propertiesandroid.useAndroidX=trueOlder setup guides also add
android.enableJetifier=true. Jetifier rewrites pre-AndroidX dependencies and is deprecated — it is off by default under AGP 8 and slows every build. Add it only if you still depend on a legacy support-library artifact.Raise
minSdkVersionto 26 inandroid/app/build.gradle:groovyandroid { defaultConfig { minSdkVersion 26 } }API 26 is what the Health Connect client library compiles against. It is not a promise that Health Connect exists on every API 26 device — the health store is a separate app, and on older devices it may be absent entirely. Always gate on
getHealthPlatformStatus()at runtime.Set
compileSdkExtension 19(required from Health Connector SDK v3.9.0, which builds against Health Connect 1.2.0-alpha03):groovy// android/app/build.gradle — Groovy DSL android { compileSdkExtension 19 }kotlin// android/app/build.gradle.kts — Kotlin DSL android { compileSdkExtension = 19 }
compileSdkExtension is compile-time only
Setting extension 19 satisfies the build. Separately, writing a non-null ExerciseSessionSegmentEvent.weight, .setIndex, or .rateOfPerceivedExertion performs a runtime device check and throws UnsupportedOperationException if that device's Health Connect Mainline module is below SDK Extension 21. The same binary can succeed on one Android 14 device and fail on another. See exercise segment extended fields.
Verify the setup
Before requesting anything, confirm the device actually has a usable health store:
final status = await HealthConnector.getHealthPlatformStatus();
if (status != HealthPlatformStatus.available) {
// On Android this commonly means Health Connect is missing or out of date.
await HealthConnector.launchHealthAppPageInAppStore();
return;
}
final connector = await HealthConnector.create();If this returns anything other than available, or your first call throws, Setup troubleshooting maps each symptom to its cause.