Interval data Fetch health data aggregated into fixed time intervals using getHealthIntervalDataFromTypes.
getHealthIntervalDataFromTypes aggregates data into fixed-length buckets, such as hourly or daily. How each bucket is aggregated, for example as a sum or an average, is defined by the native APIs. Use it for charts, summaries and trend analysis.
Future < List < HealthDataPoint >> getHealthIntervalDataFromTypes ({
required DateTime startDate ,
required DateTime endDate ,
required List < HealthDataType > types ,
required int interval ,
List < RecordingMethod > recordingMethodsToFilter = const [] ,
})
The method returns a List<HealthDataPoint> in which each point holds the aggregated data for one interval:
Values are summed, averaged or otherwise aggregated according to the platform rules; see Aggregation behavior .
Each point's dateFrom and dateTo span the interval.
Duplicates are removed.
const int INTERVAL_HOUR = 3600 ; // 1 hour
const int INTERVAL_DAY = 86400 ; // 1 day (24 hours)
const int INTERVAL_WEEK = 604800 ; // 1 week (7 days)
const int INTERVAL_MONTH = 2592000 ; // ~30 days
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' ) ;
}
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' ) ;
}
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 type
final 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
}
}
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}) ;
}
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 ,
} ;
}
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
) ;
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
) ;
// Approximate 30-day intervals
final monthly = await health . getHealthIntervalDataFromTypes (
startDate : DateTime . now () . subtract ( Duration (days : 365 )) ,
endDate : DateTime . now () ,
types : [ HealthDataType . WEIGHT ] ,
interval : 2592000 , // ~30 days in seconds
) ;
final autoSteps = await health . getHealthIntervalDataFromTypes (
startDate : DateTime . now () . subtract ( Duration (days : 7 )) ,
endDate : DateTime . now () ,
types : [ HealthDataType . STEPS ] ,
interval : 86400 ,
recordingMethodsToFilter : [ RecordingMethod . manual] ,
) ;
final platformFiltered = await health . getHealthIntervalDataFromTypes (
startDate : start ,
endDate : end ,
types : [ HealthDataType . WORKOUT ] ,
interval : 86400 ,
recordingMethodsToFilter : Platform . isAndroid
? [ RecordingMethod . unknown , RecordingMethod . manual]
: [ RecordingMethod . manual] ,
) ;
How a bucket's value is computed depends on the data type:
// Daily step totals
final dailySteps = await health . getHealthIntervalDataFromTypes (
startDate : start ,
endDate : end ,
types : [ HealthDataType . STEPS ] ,
interval : 86400 ,
) ;
// Each point = sum of all steps in that day
// Hourly average heart rate
final hourlyHR = await health . getHealthIntervalDataFromTypes (
startDate : start ,
endDate : end ,
types : [ HealthDataType . HEART_RATE ] ,
interval : 3600 ,
) ;
// Each point = average of all HR measurements in that hour
// 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 day
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 }
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 ;
}) ;
}
// Usage
final 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 } ' ) ;
}
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 ;
}
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] ;
}
}
Large ranges with small intervals return many points and can use a lot of memory:
// Large ranges with small intervals can use lots of memory
// BAD: 1-year range with hourly intervals = 8,760 points
final 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 points
final reasonable = await health . getHealthIntervalDataFromTypes (
startDate : DateTime . now () . subtract ( Duration (days : 365 )) ,
endDate : DateTime . now () ,
types : [ HealthDataType . HEART_RATE ] ,
interval : 604800 , // Weekly
) ;
Android iOS
Uses the Health Connect aggregate APIs.
May return fewer points if data is sparse.
Aggregation follows Health Connect semantics.
Uses HealthKit statistics queries.
Very efficient for large time ranges.
Aggregation follows HealthKit rules.
Validate the interval and time range, and return an empty list on errors:
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 [] ;
}
}