Future<List<HealthDataPoint>> getHealthDataFromTypes({
required List<HealthDataType> types,
required DateTime startTime,
required DateTime endTime,
Map<HealthDataType, HealthDataUnit>? preferredUnits,
List<RecordingMethod> recordingMethodsToFilter = const [],
})
Parameters
| Parameter | Type | Required | Description |
|---|
types | List<HealthDataType> | Yes | Health data types to query |
startTime | DateTime | Yes | Start of time range (inclusive) |
endTime | DateTime | Yes | End of time range (inclusive) |
preferredUnits | Map<HealthDataType, HealthDataUnit>? | No | Preferred units per type (iOS only) |
recordingMethodsToFilter | List<RecordingMethod> | No | Recording methods to exclude |
Return Value
Returns List<HealthDataPoint> containing:
- Raw data points from native health platform
- Automatically deduplicated
- Sorted by date (implementation-dependent)
Each HealthDataPoint includes:
value: Health value (numeric, workout, audiogram, etc.)
type: Data type
unit: Measurement unit
dateFrom/dateTo: Time range
sourcePlatform: iOS or Android
sourceDeviceId: Device identifier
sourceId/sourceName: Data source info
recordingMethod: How data was recorded
metadata: Platform-specific metadata
workoutSummary: For workout data
deviceModel: Device name (iOS only)
Basic Examples
Single Type Query
final heartRatePoints = await health.getHealthDataFromTypes(
types: [HealthDataType.HEART_RATE],
startTime: DateTime.now().subtract(Duration(hours: 24)),
endTime: DateTime.now(),
);
print('Found ${heartRatePoints.length} heart rate measurements');
for (var point in heartRatePoints) {
final value = (point.value as NumericHealthValue).numericValue;
print('Heart rate: $value ${point.unit.name} at ${point.dateTo}');
}
Multiple Types Query
final vitalSigns = await health.getHealthDataFromTypes(
types: [
HealthDataType.HEART_RATE,
HealthDataType.BLOOD_OXYGEN,
HealthDataType.RESPIRATORY_RATE,
HealthDataType.BODY_TEMPERATURE,
],
startTime: DateTime.now().subtract(Duration(days: 7)),
endTime: DateTime.now(),
);
// Group by type
final byType = <HealthDataType, List<HealthDataPoint>>{};
for (var point in vitalSigns) {
byType.putIfAbsent(point.type, () => []).add(point);
}
print('Heart rate: ${byType[HealthDataType.HEART_RATE]?.length ?? 0} points');
print('Blood oxygen: ${byType[HealthDataType.BLOOD_OXYGEN]?.length ?? 0} points');
Advanced Features
Preferred Units (iOS)
On iOS, you can request data in specific units:
final data = await health.getHealthDataFromTypes(
types: [
HealthDataType.HEIGHT,
HealthDataType.WEIGHT,
HealthDataType.DISTANCE_WALKING_RUNNING,
],
startTime: start,
endTime: end,
preferredUnits: {
HealthDataType.HEIGHT: HealthDataUnit.CENTIMETER, // Instead of meters
HealthDataType.WEIGHT: HealthDataUnit.POUND, // Instead of kg
HealthDataType.DISTANCE_WALKING_RUNNING: HealthDataUnit.MILE, // Instead of meters
},
);
preferredUnits is iOS-only. On Android, data is always returned in default units.
Filter by Recording Method
Exclude data based on how it was recorded:
// Exclude manual entries
final autoData = await health.getHealthDataFromTypes(
types: [HealthDataType.STEPS, HealthDataType.DISTANCE_WALKING_RUNNING],
startTime: start,
endTime: end,
recordingMethodsToFilter: [RecordingMethod.manual],
);
// Exclude unknown sources
final knownData = await health.getHealthDataFromTypes(
types: [HealthDataType.HEART_RATE],
startTime: start,
endTime: end,
recordingMethodsToFilter: [RecordingMethod.unknown],
);
// Android: Exclude multiple methods
final activeOnlyData = await health.getHealthDataFromTypes(
types: [HealthDataType.WORKOUT],
startTime: start,
endTime: end,
recordingMethodsToFilter: [
RecordingMethod.manual,
RecordingMethod.unknown,
],
);
Value Types
Different health types return different value structures:
Numeric Values
Most health metrics are numeric:
final data = await health.getHealthDataFromTypes(
types: [HealthDataType.WEIGHT],
startTime: start,
endTime: end,
);
for (var point in data) {
final numericValue = point.value as NumericHealthValue;
print('Weight: ${numericValue.numericValue} ${point.unit.name}');
}
Workout Values
Workouts have detailed activity information:
final workouts = await health.getHealthDataFromTypes(
types: [HealthDataType.WORKOUT],
startTime: start,
endTime: end,
);
for (var point in workouts) {
final workout = point.value as WorkoutHealthValue;
print('Activity: ${workout.workoutActivityType.name}');
print('Duration: ${point.dateTo.difference(point.dateFrom)}');
if (workout.totalEnergyBurned != null) {
print('Calories: ${workout.totalEnergyBurned} ${workout.totalEnergyBurnedUnit?.name}');
}
if (workout.totalDistance != null) {
print('Distance: ${workout.totalDistance} ${workout.totalDistanceUnit?.name}');
}
if (workout.totalSteps != null) {
print('Steps: ${workout.totalSteps}');
}
// Check workout summary
if (point.workoutSummary != null) {
print('Summary: ${point.workoutSummary}');
}
}
Nutrition Values
Nutrition data includes macro and micronutrients:
final meals = await health.getHealthDataFromTypes(
types: [HealthDataType.NUTRITION],
startTime: start,
endTime: end,
);
for (var point in meals) {
final nutrition = point.value as NutritionHealthValue;
print('Meal: ${nutrition.name ?? "Unknown"}');
print('Type: ${nutrition.mealType ?? "Unknown"}');
if (nutrition.calories != null) {
print('Calories: ${nutrition.calories} kcal');
}
if (nutrition.protein != null) {
print('Protein: ${nutrition.protein}g');
}
if (nutrition.carbs != null) {
print('Carbs: ${nutrition.carbs}g');
}
if (nutrition.fat != null) {
print('Fat: ${nutrition.fat}g');
}
}
Audiogram Values (iOS)
Hearing test results:
final audiograms = await health.getHealthDataFromTypes(
types: [HealthDataType.AUDIOGRAM],
startTime: start,
endTime: end,
);
for (var point in audiograms) {
final audiogram = point.value as AudiogramHealthValue;
print('Frequencies: ${audiogram.frequencies}');
print('Left ear: ${audiogram.leftEarSensitivities} dB');
print('Right ear: ${audiogram.rightEarSensitivities} dB');
}
ECG Values (iOS)
Electrocardiogram data:
final ecgs = await health.getHealthDataFromTypes(
types: [HealthDataType.ELECTROCARDIOGRAM],
startTime: start,
endTime: end,
);
for (var point in ecgs) {
final ecg = point.value as ElectrocardiogramHealthValue;
print('Voltage readings: ${ecg.voltageValues.length}');
print('Average heart rate: ${ecg.averageHeartRate} BPM');
print('Sampling frequency: ${ecg.samplingFrequency} Hz');
print('Classification: ${ecg.classification?.name}');
// Access individual voltage readings
for (var voltage in ecg.voltageValues.take(5)) {
print(' ${voltage.timeSinceSampleStart}s: ${voltage.voltage}V');
}
}
Time Range Patterns
Recent Data
// Last hour
final lastHour = await health.getHealthDataFromTypes(
types: [HealthDataType.HEART_RATE],
startTime: DateTime.now().subtract(Duration(hours: 1)),
endTime: DateTime.now(),
);
// Last 24 hours
final last24h = await health.getHealthDataFromTypes(
types: [HealthDataType.STEPS],
startTime: DateTime.now().subtract(Duration(hours: 24)),
endTime: DateTime.now(),
);
// Last 7 days
final lastWeek = await health.getHealthDataFromTypes(
types: [HealthDataType.WEIGHT],
startTime: DateTime.now().subtract(Duration(days: 7)),
endTime: DateTime.now(),
);
Calendar-Based Ranges
final now = DateTime.now();
// Today (since midnight)
final today = await health.getHealthDataFromTypes(
types: [HealthDataType.STEPS],
startTime: DateTime(now.year, now.month, now.day),
endTime: now,
);
// This week (Monday to now)
final weekday = now.weekday; // 1 = Monday, 7 = Sunday
final thisWeekStart = now.subtract(Duration(days: weekday - 1));
final thisWeek = await health.getHealthDataFromTypes(
types: [HealthDataType.ACTIVE_ENERGY_BURNED],
startTime: DateTime(thisWeekStart.year, thisWeekStart.month, thisWeekStart.day),
endTime: now,
);
// This month
final thisMonth = await health.getHealthDataFromTypes(
types: [HealthDataType.WORKOUT],
startTime: DateTime(now.year, now.month, 1),
endTime: now,
);
// This year
final thisYear = await health.getHealthDataFromTypes(
types: [HealthDataType.WEIGHT],
startTime: DateTime(now.year, 1, 1),
endTime: now,
);
Custom Ranges
// Specific date range
final custom = await health.getHealthDataFromTypes(
types: [HealthDataType.BLOOD_PRESSURE_SYSTOLIC],
startTime: DateTime(2024, 1, 1),
endTime: DateTime(2024, 12, 31),
);
// Between two timestamps
final between = await health.getHealthDataFromTypes(
types: [HealthDataType.HEART_RATE],
startTime: DateTime.parse('2024-06-01T00:00:00'),
endTime: DateTime.parse('2024-06-30T23:59:59'),
);
Data Processing
Calculate Statistics
Future<HealthStats> calculateStats(
List<HealthDataPoint> data,
) async {
if (data.isEmpty) {
return HealthStats.empty();
}
final values = data
.map((p) => (p.value as NumericHealthValue).numericValue.toDouble())
.toList();
values.sort();
final sum = values.reduce((a, b) => a + b);
final mean = sum / values.length;
final min = values.first;
final max = values.last;
final median = values[values.length ~/ 2];
return HealthStats(
count: values.length,
sum: sum,
mean: mean,
min: min,
max: max,
median: median,
);
}
// Usage
final heartRate = await health.getHealthDataFromTypes(
types: [HealthDataType.HEART_RATE],
startTime: DateTime.now().subtract(Duration(days: 7)),
endTime: DateTime.now(),
);
final stats = await calculateStats(heartRate);
print('Average heart rate: ${stats.mean.toStringAsFixed(1)} BPM');
print('Range: ${stats.min} - ${stats.max} BPM');
Group by Date
Map<DateTime, List<HealthDataPoint>> groupByDate(
List<HealthDataPoint> data,
) {
final grouped = <DateTime, List<HealthDataPoint>>{};
for (var point in data) {
final date = DateTime(
point.dateTo.year,
point.dateTo.month,
point.dateTo.day,
);
grouped.putIfAbsent(date, () => []).add(point);
}
return grouped;
}
// Usage
final steps = await health.getHealthDataFromTypes(
types: [HealthDataType.STEPS],
startTime: DateTime.now().subtract(Duration(days: 30)),
endTime: DateTime.now(),
);
final byDate = groupByDate(steps);
for (var entry in byDate.entries) {
final dailyTotal = entry.value
.map((p) => (p.value as NumericHealthValue).numericValue)
.reduce((a, b) => a + b);
print('${entry.key.toLocal()}: $dailyTotal steps');
}
Filter by Source
List<HealthDataPoint> filterBySource(
List<HealthDataPoint> data,
String sourceName,
) {
return data.where((p) => p.sourceName == sourceName).toList();
}
// Get all Apple Watch data
final allData = await health.getHealthDataFromTypes(...);
final watchData = filterBySource(allData, 'Apple Watch');
iOS: Sleep Data
Sleep types auto-convert to minutes based on time range:
final sleep = await health.getHealthDataFromTypes(
types: [
HealthDataType.SLEEP_ASLEEP,
HealthDataType.SLEEP_AWAKE,
HealthDataType.SLEEP_REM,
HealthDataType.SLEEP_DEEP,
],
startTime: DateTime.now().subtract(Duration(days: 1)),
endTime: DateTime.now(),
);
for (var point in sleep) {
final minutes = (point.value as NumericHealthValue).numericValue;
print('${point.type.name}: ${minutes} minutes');
print(' From: ${point.dateFrom}');
print(' To: ${point.dateTo}');
}
Android: BMI Calculation
On Android, BMI is computed from weight and height:
// Requesting BMI automatically fetches weight and height
final bmiData = await health.getHealthDataFromTypes(
types: [HealthDataType.BODY_MASS_INDEX],
startTime: start,
endTime: end,
);
// BMI is calculated using last observed height
// and all weight measurements in the range
Error Handling
Future<List<HealthDataPoint>> fetchDataSafely({
required List<HealthDataType> types,
required DateTime start,
required DateTime end,
}) async {
try {
// Validate time range
if (start.isAfter(end)) {
throw ArgumentError('Start time must be before end time');
}
// Check permissions
final hasPerms = await health.hasPermissions(types);
if (hasPerms != true) {
final granted = await health.requestAuthorization(types);
if (!granted) {
throw StateError('Permissions denied');
}
}
// Fetch data
final data = await health.getHealthDataFromTypes(
types: types,
startTime: start,
endTime: end,
);
return data;
} on HealthException catch (e) {
print('Health error for ${e.dataType}: ${e.cause}');
rethrow;
} on PlatformException catch (e) {
print('Platform error: ${e.code} - ${e.message}');
rethrow;
} catch (e) {
print('Unexpected error: $e');
rethrow;
}
}
- Limit time ranges: Smaller ranges = faster queries
- Be specific with types: Only request what you need
- Use filtering: Filter by recording method to reduce data
- Cache results: Store recent queries in memory
- Paginate large ranges: Break into chunks for year+ queries
See Also