health_connector_hk_ios
📖 Overview
health_connector_hk_ios is the iOS platform implementation for the Health Connector plugin. It
provides integration with Apple's HealthKit framework, enabling Flutter apps to read, write, and
aggregate health data on iOS devices.
✨ Features
| Feature | Description |
|---|---|
| 🔐 Permission Management | Request read/write permissions for health data types |
| 📖 Reading Health Data | Read a single health record by ID or multiple health records within a date/time range with pagination |
| ✍️ Writing Health Data | Write health records with metadata |
| 🔄 Updating Health Records | Modify existing records (delete-then-insert pattern) |
| 🗑️ Deleting Health Records | Remove specific records by their IDs or within a date/time range |
| ➕ Aggregating Health Data | Sum/Avg/Min/Max Aggregation |
🎯 Requirements
- Flutter >=3.3.0
- Dart >=3.9.2
- iOS >=15.0
- Xcode >=14.0
🤔 Why iOS 15.0?
Although Swift's concurrency features can be back-deployed to iOS 13.0+ using Xcode 13.2+, we intentionally set iOS 15.0 as the minimum supported version to ensure:
- Full access to the native Swift concurrency runtime without back-deployment shims
- A simpler and more reliable runtime environment with no compatibility layers
- Better long-term maintainability and reduced technical debt
Note: HealthKit itself has been available since iOS 8.0. The iOS 15.0 requirement is a conscious architectural decision driven by native concurrency support - not a limitation of HealthKit.
🚀 Getting Started
📦 Installation
Add to your pubspec.yaml:
flutter pub add health_connector_hk_ios
Or manually add:
dependencies:
health_connector_hk_ios: [latest_version]
⚙️ iOS Setup
Enable HealthKit Capability
Open your project in Xcode (ios/Runner.xcworkspace) and:
- Select your app target
- Go to Signing & Capabilities tab
- Click + Capability
- Add HealthKit
Update Info.plist
Add usage descriptions to ios/Runner/Info.plist:
<dict>
<!-- Existing keys -->
<!-- HealthKit usage description -->
<key>NSHealthShareUsageDescription</key>
<string>This app needs to read your health data to provide personalized insights.</string>
<key>NSHealthUpdateUsageDescription</key>
<string>This app needs to save health data to track your progress.</string>
<!-- Optional: For background delivery -->
<key>UIBackgroundModes</key>
<array>
<string>processing</string>
</array>
</dict>
Important: Provide clear, user-friendly descriptions. Vague descriptions may result in App Store rejection.
📚 Usage
Import Package
import 'package:health_connector/health_connector.dart';
Create HealthConnectorHKClient Instance
final client = HealthConnectorHKClient();
Check Platform Availability
final status = await client.getHealthPlatformStatus();
switch (status) {
case HealthPlatformStatus.available:
print('HealthKit ready');
break;
case HealthPlatformStatus.unavailable:
print('HealthKit not supported on this device');
break;
case HealthPlatformStatus.installationOrUpdateRequired:
print('Please install or update Apple Health');
break;
}
Check Health Platform Feature Status
Note: On iOS HealthKit, all health platform features are available by default. The
getFeatureStatus()always returnsHealthPlatformFeatureStatus.availablefor all features.
// Feature status check - always returns available on iOS
final status = await client.getFeatureStatus(
HealthPlatformFeature.readHealthDataInBackground,
);
// status == HealthPlatformFeatureStatus.available
Permissions
Note:
- On iOS HealthKit, all health platform features are granted by default. Requesting feature permissions always returns
PermissionStatus.granted.- Read permissions always return
PermissionStatus.unknownfor privacy reasons.
Request Permissions
final permissions = [
HealthDataType.steps.readPermission,
HealthDataType.steps.writePermission,
HealthDataType.weight.readPermission,
HealthDataType.weight.writePermission,
// ...
];
final results = await client.requestPermissions(permissions);
for (final result in results) {
print('${result.permission}: ${result.status}');
}
Read Health Records
Read Health Record by ID
final recordId = HealthRecordId('existing-record-id');
final readRequest = HealthDataType.steps.readRecord(recordId);
final record = await client.readRecord(readRequest);
if (record != null) {
print('Steps: ${record.count.value} from ${record.startTime} to ${record.endTime}');
} else {
print('Record not found');
}
Read Multiple Health Records
final request = HealthDataType.steps.readRecords(
startTime: DateTime.now().subtract(Duration(days: 7)),
endTime: DateTime.now(),
pageSize: 100,
);
final response = await client.readRecords(request);
for (final record in response.records) {
print('Steps: ${record.count.value} from ${record.startTime} to ${record.endTime}');
}
Read Multiple Health Records with Pagination
var request = HealthDataType.steps.readRecords(
startTime: DateTime.now().subtract(Duration(days: 30)),
endTime: DateTime.now(),
pageSize: 100,
);
var allRecords = <StepRecord>[];
var pageNumber = 1;
while (true) {
final response = await client.readRecords(request);
allRecords.addAll(response.records.cast<StepRecord>());
if (response.nextPageRequest == null || response.records.isEmpty) {
break;
}
request = response.nextPageRequest!;
pageNumber++;
}
print('Total records fetched: ${allRecords.length}');
Write Health Records
Write Single Health Record
final stepRecord = StepRecord(
id: HealthRecordId.none,
startTime: DateTime.now().subtract(Duration(hours: 1)),
endTime: DateTime.now(),
count: Numeric(5000),
metadata: Metadata.automaticallyRecorded(
device: Device.fromType(DeviceType.phone),
),
);
final recordId = await client.writeRecord(stepRecord);
print('Record written with ID: $recordId');
Write Multiple Health Records
final records = [
StepRecord(
id: HealthRecordId.none,
startTime: DateTime.now().subtract(Duration(hours: 2)),
endTime: DateTime.now().subtract(Duration(hours: 1)),
count: Numeric(3000),
metadata: Metadata.automaticallyRecorded(
device: Device.fromType(DeviceType.phone),
),
),
DistanceRecord(
id: HealthRecordId.none,
startTime: DateTime.now().subtract(Duration(hours: 1)),
endTime: DateTime.now(),
distance: Length(5000),
metadata: Metadata.automaticallyRecorded(
device: Device.fromType(DeviceType.phone),
),
),
// ...
];
final recordIds = await client.writeRecords(records);
print('Wrote ${recordIds.length} records');
Update Health Records
final recordId = HealthRecordId('existing-record-id');
final readRequest = HealthDataType.steps.readRecord(recordId);
final existingRecord = await client.readRecord(readRequest);
if (existingRecord != null) {
final originalId = existingRecord.id;
final updatedRecord = existingRecord.copyWith(
startTime: existingRecord.startTime,
endTime: existingRecord.endTime,
count: Numeric(existingRecord.count.value + 100),
metadata: existingRecord.metadata,
);
final newId = await client.updateRecord(updatedRecord);
print('Original ID: $originalId');
print('New ID: $newId');
// originalId != newId on iOS
}
Important: HealthKit uses an immutable data model. Existing samples cannot be modified after being saved. The plugin implements updates as delete-then-insert, so the record ID changes after update (new UUID assigned).
Delete Health Records
Delete Health Records by IDs
await client.deleteRecordsByIds(
dataType: HealthDataType.steps,
recordIds: [
HealthRecordId('id-1'),
HealthRecordId('id-2'),
],
);
print('Records deleted');
Delete Health Records by Date Range
await client.deleteRecords(
dataType: HealthDataType.steps,
startTime: DateTime.now().subtract(Duration(days: 7)),
endTime: DateTime.now(),
);
print('Records deleted');
Aggregate Health Data
final sumRequest = HealthDataType.steps.aggregateSum(
startTime: DateTime.now().startOfDay,
endTime: DateTime.now(),
);
final sumResponse = await client.aggregate(sumRequest);
print('Total steps today: ${sumResponse.value.value}');
final avgRequest = HealthDataType.weight.aggregateAvg(
startTime: DateTime.now().subtract(Duration(days: 30)),
endTime: DateTime.now(),
);
final avgResponse = await client.aggregate(avgRequest);
print('Average weight: ${avgResponse.value.inKilograms} kg');
final minRequest = HealthDataType.weight.aggregateMin(
startTime: DateTime.now().subtract(Duration(days: 30)),
endTime: DateTime.now(),
);
final minResponse = await client.aggregate(minRequest);
print('Min weight: ${minResponse.value.inKilograms} kg');
final maxRequest = HealthDataType.weight.aggregateMax(
startTime: DateTime.now().subtract(Duration(days: 30)),
endTime: DateTime.now(),
);
final maxResponse = await client.aggregate(maxRequest);
print('Max weight: ${maxResponse.value.inKilograms} kg');
📋 Supported Health Data Types
Note: For a complete list of all HealthKit data types, see the official HealthKit documentation.
🏃 Activity
| Data Type | Supported | Documentation |
|---|---|---|
| Steps | ✅ | HKQuantityTypeIdentifier.stepCount |
| Distance | ✅ | HKQuantityTypeIdentifier.distanceWalkingRunning |
| Active Calories Burned | ✅ | HKQuantityTypeIdentifier.activeEnergyBurned |
| Floors Climbed | ✅ | HKQuantityTypeIdentifier.flightsClimbed |
| Wheelchair Pushes | ✅ | HKQuantityTypeIdentifier.pushCount |
| Workout | ❌ | HKWorkoutTypeIdentifier |
| Total Calories Burned | ❌ | HKQuantityTypeIdentifier.basalEnergyBurned |
| Power | ❌ | HKQuantityTypeIdentifier.cyclingPower |
| Speed | ❌ | HKQuantityTypeIdentifier.walkingSpeed, HKQuantityTypeIdentifier.runningSpeed |
| Distance Cycling | ❌ | HKQuantityTypeIdentifier.distanceCycling |
| Distance Swimming | ❌ | HKQuantityTypeIdentifier.distanceSwimming |
| Distance Wheelchair | ❌ | HKQuantityTypeIdentifier.distanceWheelchair |
| Apple Exercise Time | ❌ | HKQuantityTypeIdentifier.appleExerciseTime |
| Apple Stand Time | ❌ | HKQuantityTypeIdentifier.appleStandTime |
| Nike Fuel | ❌ | HKQuantityTypeIdentifier.nikeFuel |
| Swimming Stroke Count | ❌ | HKQuantityTypeIdentifier.swimmingStrokeCount |
📏 Body Measurements
| Data Type | Supported | Documentation |
|---|---|---|
| Weight | ✅ | HKQuantityTypeIdentifier.bodyMass |
| Height | ✅ | HKQuantityTypeIdentifier.height |
| Body Fat Percentage | ✅ | HKQuantityTypeIdentifier.bodyFatPercentage |
| Lean Body Mass | ✅ | HKQuantityTypeIdentifier.leanBodyMass |
| Body Mass Index | ❌ | HKQuantityTypeIdentifier.bodyMassIndex |
| Waist Circumference | ❌ | HKQuantityTypeIdentifier.waistCircumference |
🩸 Cycle Tracking / Reproductive Health
| Data Type | Supported | Documentation |
|---|---|---|
| Menstruation Flow | ❌ | HKCategoryTypeIdentifier.menstrualFlow |
| Cervical Mucus | ❌ | HKCategoryTypeIdentifier.cervicalMucusQuality |
| Ovulation Test | ❌ | HKCategoryTypeIdentifier.ovulationTestResult |
| Basal Body Temperature | ❌ | HKQuantityTypeIdentifier.basalBodyTemperature |
| Sexual Activity | ❌ | HKCategoryTypeIdentifier.sexualActivity |
| Intermenstrual Bleeding | ❌ | HKCategoryTypeIdentifier.intermenstrualBleeding |
| Pregnancy Test Result | ❌ | HKCategoryTypeIdentifier.pregnancyTestResult |
| Progesterone Test Result | ❌ | HKCategoryTypeIdentifier.progesteroneTestResult |
🍎 Nutrition
😴 Sleep
| Data Type | Supported | Documentation |
|---|---|---|
| Sleep Stage | ✅ | HKCategoryTypeIdentifier.sleepAnalysis |
❤️ Vitals
🧘 Wellness / Mental Health
| Data Type | Supported | Documentation |
|---|---|---|
| Mindfulness Session | ❌ | HKCategoryTypeIdentifier.mindfulSession |
🏥 Clinical Records
| Data Type | Supported | Documentation |
|---|---|---|
| Electrocardiogram (ECG) | ❌ | HKElectrocardiogram |
| Vision Prescription | ❌ | HKVisionPrescription |
🎧 Audio Exposure
| Data Type | Supported | Documentation |
|---|---|---|
| Headphone Audio Exposure | ❌ | HKQuantityTypeIdentifier.headphoneAudioExposureEvent |
| Environmental Audio Exposure | ❌ | HKQuantityTypeIdentifier.environmentalAudioExposureEvent |
🚶 Mobility & Gait Analysis
| Data Type | Supported | Documentation |
|---|---|---|
| Walking Speed | ❌ | HKQuantityTypeIdentifier.walkingSpeed |
| Running Speed | ❌ | HKQuantityTypeIdentifier.runningSpeed |
| Step Length | ❌ | HKQuantityTypeIdentifier.stepLength |
| Walking Asymmetry Percentage | ❌ | HKQuantityTypeIdentifier.walkingAsymmetryPercentage |
| Walking Double Support Percentage | ❌ | HKQuantityTypeIdentifier.walkingDoubleSupportPercentage |
| Six Minute Walk Test Distance | ❌ | HKQuantityTypeIdentifier.sixMinuteWalkTestDistance |
| Stair Ascent Speed | ❌ | HKQuantityTypeIdentifier.stairAscentSpeed |
| Stair Descent Speed | ❌ | HKQuantityTypeIdentifier.stairDescentSpeed |
🤝 Contributing
Contributions are welcome!
To report issues or request features, please visit our GitHub Issues.