public final class AggregateQuery

  1. Object
  2. AggregateQuery

Describes a bucketed summary read against HealthStore.

AggregateQuery q = new AggregateQuery()
        .addType(HealthDataType.STEPS)
        .addMetric(AggregateMetric.TOTAL)
        .setTimeRange(HealthTimeRange.calendarDays(7, ZoneId.systemDefault()))
        .setBucket(HealthInterval.calendarDays(1, ZoneId.systemDefault()));

Overlapping sources are counted twice, on every platform

When a phone and a watch both record steps for the same walk, the store holds two overlapping sets of samples, and a total over them counts the walk twice.

This includes iOS. HealthKit’s statistics engine does de-duplicate overlapping sources – but no port uses it in this release. Every metric here is computed by shared code from raw samples read back through an ordinary query, so that the bucket arithmetic has one implementation rather than one per platform that can drift. iOS therefore double-counts exactly as Android does. A port that grows a native aggregate path would change that, and this note with it.

This API does not paper over the overlap with a heuristic de-duplicator – guessing which of two overlapping sources is authoritative is exactly the kind of silent wrongness health data cannot afford. Use addSource(String) to pin the query to the source you trust, and tell the user which device a figure came from.

Constructors

public AggregateQuery()

Methods

public AggregateQuery addType(HealthDataType type)Adds a type to summarize.
public List<HealthDataType> getTypes()The types this query summarizes.
public AggregateQuery addMetric(AggregateMetric metric)Adds a metric to compute.
public List<AggregateMetric> getMetrics()The metrics this query computes.
public AggregateQuery setDeduplicateSources(boolean deduplicate)Restricts the summary to one writing app – see the double-counting warning on this class.
public boolean isDeduplicateSources()Whether this query asked for source de-duplication.
public AggregateQuery addSource(String bundleId)
public List<String> getSources()The source filter, empty when unrestricted.
public AggregateQuery setTimeRange(HealthTimeRange timeRange)The span to summarize.
public HealthTimeRange getTimeRange()The span this query summarizes.
public AggregateQuery setBucket(HealthInterval bucket)Splits the range into buckets of this width.
public HealthInterval getBucket()The bucket width, or null for one bucket over the whole range.
public AggregateQuery setUnit(HealthUnit unit)Returns aggregated values in unit rather than each type’s canonical unit.
public HealthUnit getUnit()The requested unit, or null for canonical units.
public void validate() throws HealthExceptionValidates the query and throws if it cannot be run.
public static boolean isMeaningful(HealthDataType type, AggregateMetric metric)Whether metric says anything true about type.

Inherited methods

Constructor details

AggregateQuery

public AggregateQuery()

Method details

addType

public AggregateQuery addType(HealthDataType type)
Adds a type to summarize. At least one is required.

getTypes

public List<HealthDataType> getTypes()
The types this query summarizes.

addMetric

public AggregateQuery addMetric(AggregateMetric metric)
Adds a metric to compute. At least one is required.

getMetrics

public List<AggregateMetric> getMetrics()
The metrics this query computes.

setDeduplicateSources

public AggregateQuery setDeduplicateSources(boolean deduplicate)

Restricts the summary to one writing app – see the double-counting warning on this class. Asks the platform to de-duplicate overlapping sources.

Off by default, because the shared rollup cannot do it and most stores therefore cannot honour it. Check HealthStore.isSourceDeduplicationSupported() first: asking a store that cannot fails the aggregate with HealthError.NOT_SUPPORTED rather than silently returning the double-counted answer.

Where it is honoured – HealthKit, whose statistics engine does this natively – a walk recorded by both a phone and a watch is counted once instead of twice.

isDeduplicateSources

public boolean isDeduplicateSources()
Whether this query asked for source de-duplication.

addSource

public AggregateQuery addSource(String bundleId)

getSources

public List<String> getSources()
The source filter, empty when unrestricted.

setTimeRange

public AggregateQuery setTimeRange(HealthTimeRange timeRange)
The span to summarize. Required.

getTimeRange

public HealthTimeRange getTimeRange()
The span this query summarizes.

setBucket

public AggregateQuery setBucket(HealthInterval bucket)

Splits the range into buckets of this width. Leave unset for a single bucket covering the whole range.

Prefer HealthInterval.calendarDays(int,java.time.ZoneId) over a fixed 24-hour width whenever the buckets are labelled with dates in your UI – see HealthInterval.

getBucket

public HealthInterval getBucket()
The bucket width, or null for one bucket over the whole range.

setUnit

public AggregateQuery setUnit(HealthUnit unit)
Returns aggregated values in unit rather than each type’s canonical unit.

getUnit

public HealthUnit getUnit()
The requested unit, or null for canonical units.

validate

public void validate() throws HealthException
Validates the query and throws if it cannot be run.

Throws

HealthException
HealthError.INVALID_ARGUMENT for a missing type, metric or range, or for a metric that is meaningless for the requested type; HealthError.UNIT_MISMATCH for an incompatible unit.

isMeaningful

public static boolean isMeaningful(HealthDataType type, AggregateMetric metric)

Whether metric says anything true about type.

AggregateMetric.COUNT and AggregateMetric.DURATION apply to everything. Summing a discrete series – the total of every body mass ever recorded – and averaging a cumulative one – the mean of arbitrarily-chunked step totals – are both meaningless, so they are rejected rather than answered.