Interval Data
Future<List<HealthDataPoint>> getHealthIntervalDataFromTypes({ required DateTime startDate, required DateTime endDate, required List<HealthDataType> types, required int interval, List<RecordingMethod> recordingMethodsToFilter = const [],})Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
startDate |
DateTime |
Yes | Start of time range |
endDate |
DateTime |
Yes | End of time range |
types |
List<HealthDataType> |
Yes | Health data types to query |
interval |
int |
Yes | Interval duration in seconds |
recordingMethodsToFilter |
List<RecordingMethod> |
No | Recording methods to exclude |
Return Value
Section titled “Return Value”Returns List<HealthDataPoint> where each point represents aggregated data for one interval:
- Values are summed, averaged, or aggregated per platform rules
- Each point’s
dateFrom/dateTospan the interval - Duplicate removal applied
Common Intervals
Section titled “Common Intervals”const int INTERVAL_HOUR = 3600; // 1 hourconst int INTERVAL_DAY = 86400; // 1 day (24 hours)const int INTERVAL_WEEK = 604800; // 1 week (7 days)const int INTERVAL_MONTH = 2592000; // ~30 daysBasic Example
Section titled “Basic Example”Daily Aggregation
Section titled “Daily Aggregation”final dailySteps = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 7)), endDate: DateTime.now(), types: [HealthDataType.STEPS], interval: 86400, // 1 day in seconds);
print('Daily step counts for last 7 days:');for (var point in dailySteps) { final steps = (point.value as NumericHealthValue).numericValue; print('${point.dateFrom.toLocal()}: $steps steps');}Hourly Aggregation
Section titled “Hourly Aggregation”final hourlyHeartRate = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 1)), endDate: DateTime.now(), types: [HealthDataType.HEART_RATE], interval: 3600, // 1 hour in seconds);
print('Hourly average heart rate:');for (var point in hourlyHeartRate) { final hr = (point.value as NumericHealthValue).numericValue; print('${point.dateFrom.hour}:00 - ${point.dateTo.hour}:00: ${hr.toStringAsFixed(1)} BPM');}Use Cases
Section titled “Use Cases”Charts & Graphs
Section titled “Charts & Graphs”Perfect for visualizations:
Future<List<ChartData>> getWeeklyStepsChart() async { final data = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 7)), endDate: DateTime.now(), types: [HealthDataType.STEPS], interval: 86400, // Daily );
return data.map((point) { final steps = (point.value as NumericHealthValue).numericValue; return ChartData( date: point.dateFrom, value: steps.toDouble(), ); }).toList();}
class ChartData { final DateTime date; final double value;
ChartData({required this.date, required this.value});}Weekly Summary
Section titled “Weekly Summary”Future<Map<String, dynamic>> getWeeklySummary() async { final data = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 7)), endDate: DateTime.now(), types: [ HealthDataType.STEPS, HealthDataType.ACTIVE_ENERGY_BURNED, HealthDataType.DISTANCE_WALKING_RUNNING, ], interval: 86400, // Daily buckets );
// Group by type final byType = <HealthDataType, List<HealthDataPoint>>{}; for (var point in data) { byType.putIfAbsent(point.type, () => []).add(point); }
// Calculate totals double totalSteps = 0; double totalCalories = 0; double totalDistance = 0;
for (var point in byType[HealthDataType.STEPS] ?? []) { totalSteps += (point.value as NumericHealthValue).numericValue; }
for (var point in byType[HealthDataType.ACTIVE_ENERGY_BURNED] ?? []) { totalCalories += (point.value as NumericHealthValue).numericValue; }
for (var point in byType[HealthDataType.DISTANCE_WALKING_RUNNING] ?? []) { totalDistance += (point.value as NumericHealthValue).numericValue; }
return { 'steps': totalSteps, 'calories': totalCalories, 'distance': totalDistance, 'days': byType[HealthDataType.STEPS]?.length ?? 0, };}Multiple Data Types
Section titled “Multiple Data Types”Query multiple types in one call:
final intervalData = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 30)), endDate: DateTime.now(), types: [ HealthDataType.STEPS, HealthDataType.HEART_RATE, HealthDataType.BLOOD_OXYGEN, HealthDataType.WEIGHT, ], interval: 86400, // Daily);
// Separate by typefinal stepsByDay = <DateTime, double>{};final heartRateByDay = <DateTime, double>{};
for (var point in intervalData) { final date = DateTime( point.dateFrom.year, point.dateFrom.month, point.dateFrom.day, ); final value = (point.value as NumericHealthValue).numericValue.toDouble();
switch (point.type) { case HealthDataType.STEPS: stepsByDay[date] = value; break; case HealthDataType.HEART_RATE: heartRateByDay[date] = value; break; // ... handle other types }}Custom Intervals
Section titled “Custom Intervals”15-Minute Intervals
Section titled “15-Minute Intervals”final quarterHourly = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(hours: 4)), endDate: DateTime.now(), types: [HealthDataType.HEART_RATE], interval: 900, // 15 minutes in seconds);6-Hour Intervals
Section titled “6-Hour Intervals”final sixHourly = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 3)), endDate: DateTime.now(), types: [HealthDataType.BLOOD_GLUCOSE], interval: 21600, // 6 hours in seconds);Monthly Intervals
Section titled “Monthly Intervals”// Approximate 30-day intervalsfinal monthly = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 365)), endDate: DateTime.now(), types: [HealthDataType.WEIGHT], interval: 2592000, // ~30 days in seconds);Filtering
Section titled “Filtering”Exclude Manual Entries
Section titled “Exclude Manual Entries”final autoSteps = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 7)), endDate: DateTime.now(), types: [HealthDataType.STEPS], interval: 86400, recordingMethodsToFilter: [RecordingMethod.manual],);Platform-Specific Filtering
Section titled “Platform-Specific Filtering”final platformFiltered = await health.getHealthIntervalDataFromTypes( startDate: start, endDate: end, types: [HealthDataType.WORKOUT], interval: 86400, recordingMethodsToFilter: Platform.isAndroid ? [RecordingMethod.unknown, RecordingMethod.manual] : [RecordingMethod.manual],);Aggregation Behavior
Section titled “Aggregation Behavior”Sum (Cumulative) Types
Section titled “Sum (Cumulative) Types”These types are summed across the interval:
STEPSDISTANCE_WALKING_RUNNINGACTIVE_ENERGY_BURNEDBASAL_ENERGY_BURNEDFLIGHTS_CLIMBED
// Daily step totalsfinal dailySteps = await health.getHealthIntervalDataFromTypes( startDate: start, endDate: end, types: [HealthDataType.STEPS], interval: 86400,);// Each point = sum of all steps in that dayAverage (Sampling) Types
Section titled “Average (Sampling) Types”These types are averaged across the interval:
HEART_RATEBLOOD_OXYGENBLOOD_PRESSURE_*BODY_TEMPERATURE
// Hourly average heart ratefinal hourlyHR = await health.getHealthIntervalDataFromTypes( startDate: start, endDate: end, types: [HealthDataType.HEART_RATE], interval: 3600,);// Each point = average of all HR measurements in that hourLatest (Point-in-Time) Types
Section titled “Latest (Point-in-Time) Types”These types use the latest value in the interval:
WEIGHTHEIGHTBODY_FAT_PERCENTAGE
// Daily weight (last recorded value each day)final dailyWeight = await health.getHealthIntervalDataFromTypes( startDate: start, endDate: end, types: [HealthDataType.WEIGHT], interval: 86400,);// Each point = most recent weight measurement that dayData Analysis
Section titled “Data Analysis”Trend Detection
Section titled “Trend Detection”Future<HealthTrend> analyzeTrend(List<HealthDataPoint> intervalData) async { if (intervalData.length < 2) { return HealthTrend.insufficient; }
final values = intervalData .map((p) => (p.value as NumericHealthValue).numericValue.toDouble()) .toList();
// Simple linear regression double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for (int i = 0; i < values.length; i++) { sumX += i; sumY += values[i]; sumXY += i * values[i]; sumX2 += i * i; }
final n = values.length; final slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
if (slope > 0.1) return HealthTrend.increasing; if (slope < -0.1) return HealthTrend.decreasing; return HealthTrend.stable;}
enum HealthTrend { increasing, decreasing, stable, insufficient }Peak Detection
Section titled “Peak Detection”HealthDataPoint? findPeakInterval(List<HealthDataPoint> intervalData) { if (intervalData.isEmpty) return null;
return intervalData.reduce((a, b) { final aValue = (a.value as NumericHealthValue).numericValue; final bValue = (b.value as NumericHealthValue).numericValue; return aValue > bValue ? a : b; });}
// Usagefinal hourlySteps = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 1)), endDate: DateTime.now(), types: [HealthDataType.STEPS], interval: 3600,);
final peakHour = findPeakInterval(hourlySteps);if (peakHour != null) { print('Most active hour: ${peakHour.dateFrom.hour}:00'); print('Steps: ${(peakHour.value as NumericHealthValue).numericValue}');}Gap Detection
Section titled “Gap Detection”List<DateTimeRange> findDataGaps( List<HealthDataPoint> intervalData, int intervalSeconds,) { if (intervalData.isEmpty) return [];
final gaps = <DateTimeRange>[];
for (int i = 0; i < intervalData.length - 1; i++) { final current = intervalData[i].dateTo; final next = intervalData[i + 1].dateFrom;
final expectedGap = Duration(seconds: intervalSeconds); final actualGap = next.difference(current);
if (actualGap > expectedGap) { gaps.add(DateTimeRange(start: current, end: next)); } }
return gaps;}Visualization Helpers
Section titled “Visualization Helpers”Format for Charts
Section titled “Format for Charts”class IntervalChartData { final String label; final double value; final DateTime timestamp;
IntervalChartData({ required this.label, required this.value, required this.timestamp, });
static Future<List<IntervalChartData>> fromHealth({ required List<HealthDataType> types, required DateTime start, required DateTime end, required int interval, }) async { final data = await health.getHealthIntervalDataFromTypes( startDate: start, endDate: end, types: types, interval: interval, );
return data.map((point) { final value = (point.value as NumericHealthValue).numericValue.toDouble(); final label = _formatIntervalLabel(point.dateFrom, interval);
return IntervalChartData( label: label, value: value, timestamp: point.dateFrom, ); }).toList(); }
static String _formatIntervalLabel(DateTime date, int interval) { if (interval == 86400) { // Daily: "Mon", "Tue", etc. return ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][date.weekday]; } else if (interval == 3600) { // Hourly: "14:00", "15:00", etc. return '${date.hour}:00'; } else if (interval >= 604800) { // Weekly/monthly: "Jan 1", "Jan 8", etc. return '${_monthName(date.month)} ${date.day}'; } return date.toString(); }
static String _monthName(int month) { return ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][month]; }}Performance Considerations
Section titled “Performance Considerations”Optimal Interval Sizes
Section titled “Optimal Interval Sizes”- Short ranges (< 1 day): Use hourly intervals (3600s)
- Medium ranges (1-30 days): Use daily intervals (86400s)
- Long ranges (> 30 days): Use weekly/monthly intervals
Memory Usage
Section titled “Memory Usage”// Large ranges with small intervals can use lots of memory// BAD: 1-year range with hourly intervals = 8,760 pointsfinal tooMany = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 365)), endDate: DateTime.now(), types: [HealthDataType.HEART_RATE], interval: 3600, // Hourly);
// GOOD: 1-year range with weekly intervals = 52 pointsfinal reasonable = await health.getHealthIntervalDataFromTypes( startDate: DateTime.now().subtract(Duration(days: 365)), endDate: DateTime.now(), types: [HealthDataType.HEART_RATE], interval: 604800, // Weekly);Platform Differences
Section titled “Platform Differences”iOS Behavior
Section titled “iOS Behavior”- Uses HealthKit’s statistics queries
- Very efficient for large time ranges
- Aggregation follows HealthKit rules
Android Behavior
Section titled “Android Behavior”- Uses Health Connect aggregate APIs
- May return fewer points if data is sparse
- Aggregation follows Health Connect semantics
Error Handling
Section titled “Error Handling”Future<List<HealthDataPoint>> fetchIntervalDataSafely({ required List<HealthDataType> types, required DateTime start, required DateTime end, required int interval,}) async { if (interval <= 0) { throw ArgumentError('Interval must be positive'); }
if (start.isAfter(end)) { throw ArgumentError('Start must be before end'); }
try { return await health.getHealthIntervalDataFromTypes( startDate: start, endDate: end, types: types, interval: interval, ); } on HealthException catch (e) { print('Health error: ${e.dataType} - ${e.cause}'); return []; } catch (e) { print('Error: $e'); return []; }}See Also
Section titled “See Also”- Basic Reading - Individual data points
- Aggregate Reading - Multi-type summaries
- Reading Steps - Optimized step queries