Future<List<HealthDataPoint>> getHealthDataFromTypes({
  required List<HealthDataType> types,
  required DateTime startTime,
  required DateTime endTime,
  Map<HealthDataType, HealthDataUnit>? preferredUnits,
  List<RecordingMethod> recordingMethodsToFilter = const [],
})

Parameters

ParameterTypeRequiredDescription
typesList<HealthDataType>YesHealth data types to query
startTimeDateTimeYesStart of time range (inclusive)
endTimeDateTimeYesEnd of time range (inclusive)
preferredUnitsMap<HealthDataType, HealthDataUnit>?NoPreferred units per type (iOS only)
recordingMethodsToFilterList<RecordingMethod>NoRecording 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');

Platform-Specific Behavior

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;
  }
}

Performance Tips

  1. Limit time ranges: Smaller ranges = faster queries
  2. Be specific with types: Only request what you need
  3. Use filtering: Filter by recording method to reduce data
  4. Cache results: Store recent queries in memory
  5. Paginate large ranges: Break into chunks for year+ queries

See Also