Aggregating data in Health Connect includes basic aggregations or aggregating data into buckets. The following workflows show you how to do both.
Basic aggregation
To use basic aggregation on your data, use the aggregate
function
on your HealthConnectClient
object. It accepts an
AggregateRequest
object where you add the metric types
and the time range as its parameters. How basic aggregates are called depends on
the metric types used.
Cumulative aggregation
Cumulative aggregation computes the total value.
The following example shows you how to aggregate data for a data type:
suspend fun aggregateDistance(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
try {
val response = healthConnectClient.aggregate(
AggregateRequest(
metrics = setOf(DistanceRecord.DISTANCE_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
// The result may be null if no data is available in the time range
val distanceTotalInMeters = response[DistanceRecord.DISTANCE_TOTAL]?.inMeters ?: 0L
} catch (e: Exception) {
// Run error handling here
}
}
Statistical aggregation
Statistical aggregation computes the minimum, maximum, or average values of records with samples.
The following example shows how to use statistical aggregation:
suspend fun aggregateHeartRate(
healthConnectClient: HealthConnectClient,
startTime: Instant,
endTime: Instant
) {
try {
val response =
healthConnectClient.aggregate(
AggregateRequest(
setOf(HeartRateRecord.BPM_MAX, HeartRateRecord.BPM_MIN),
timeRangeFilter = TimeRangeFilter.between(startTime, endTime)
)
)
// The result may be null if no data is available in the time range
val minimumHeartRate = response[HeartRateRecord.BPM_MIN]
val maximumHeartRate = response[HeartRateRecord.BPM_MAX]
} catch (e: Exception) {
// Run error handling here
}
}
Buckets
Health Connect can also let you aggregate data into buckets. The two types of buckets you can use include duration and period.
Once called, they return a list of buckets. Note that the list can be sparse, so a bucket is not included in the list if it doesn't contain any data.
Duration
In this case, aggregated data is split into buckets within a fixed length of
time, such as a minute or an hour. To aggregate data into buckets, use
aggregateGroupByDuration
. It accepts an
AggregateGroupByDurationRequest
object where you add the
metric types, the time range, and the Duration
as parameters.
The following shows an example of aggregating steps into minute-long buckets:
suspend fun aggregateStepsIntoMinutes(
healthConnectClient: HealthConnectClient,
startTime: LocalDateTime,
endTime: LocalDateTime
) {
try {
val response =
healthConnectClient.aggregateGroupByDuration(
AggregateGroupByDurationRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
timeRangeSlicer = Duration.ofMinutes(1L)
)
)
for (durationResult in response) {
// The result may be null if no data is available in the time range
val totalSteps = durationResult.result[StepsRecord.COUNT_TOTAL]
}
} catch (e: Exception) {
// Run error handling here
}
}
Period
In this case, aggregated data is split into buckets within a date-based amount
of time, such as a week or a month. To aggregate data into buckets, use
aggregateGroupByPeriod
. It accepts an
AggregateGroupByPeriodRequest
object where you add the
metric types, the time range, and the Period
as parameters.
The following shows an example of aggregating steps into monthly buckets:
suspend fun aggregateStepsIntoMonths(
healthConnectClient: HealthConnectClient,
startTime: LocalDateTime,
endTime: LocalDateTime
) {
try {
val response =
healthConnectClient.aggregateGroupByPeriod(
AggregateGroupByPeriodRequest(
metrics = setOf(StepsRecord.COUNT_TOTAL),
timeRangeFilter = TimeRangeFilter.between(startTime, endTime),
timeRangeSlicer = Period.ofMonths(1)
)
)
for (monthlyResult in response) {
// The result may be null if no data is available in the time range
val totalSteps = monthlyResult.result[StepsRecord.COUNT_TOTAL]
}
} catch (e: Exception) {
// Run error handling here
}
}
Read restrictions
By default, your app can read data up to 30 days with any permissions granted.
With the PERMISSION_READ_HEALTH_DATA_HISTORY
permission, your
app can read data older than 30 days.
30-day restriction
Applications can read data from Health Connect for up to 30 days prior to when any permission was first granted.
However, if a user deletes your app, the permission history is lost. If the user reinstalls your app and grants permission again, your app can read data from Health Connect up to 30 days prior to that new date.
30-day example
If a user first granted read permission to your application on March 30, 2023, the earliest data your app could read back would be from February 28, 2023 onwards.
The user then deletes your app on May 10, 2023. The user decides to reinstall it on May 15, 2023 and grant read permission. The earliest date your app can now read data from is April 15, 2023.
Read data older than 30 days
If you would like to read data older than 30 days, you must use the
PERMISSION_READ_HEALTH_DATA_HISTORY
permission. Without this permission,
an attempt to read a single record older than 30 days results in an error.
You also can't read any data older than 30 days using one of the time range
requests.