# Class HealthStore

java.lang.Object
com.codename1.health.HealthStore

public class HealthStore extends Object

Reads, writes and watches the platform health store -- HealthKit on iOS, Health Connect on Android, a local store elsewhere.

Obtain one from `Health.getStore()`; it is never null. On a port with no health support this base class is returned as-is and every operation fails fast with `HealthError.NOT_SUPPORTED`.

##### Division of labour

The public methods here are `final`. They validate requests, normalize units, page through results, compute bucket boundaries, own the subscription registry and persist cursors -- all of it once, in shared code. Ports implement only the `do*` methods, which receive already-validated input and hand back raw platform data.

That split is deliberate: it is why a malformed query fails the same way on every platform, why unit conversion cannot drift between ports, and why no port can forget to check that the health provider is actually available.

##### Threading

Every method may be called from the EDT and returns immediately. Every callback arrives on the EDT. Post-processing of large result sets -- unit conversion across a hundred thousand samples -- happens on a shared background thread, so nothing here blocks the UI.

- ## Field Summary

  Fields
  Modifier and Type
  Field
  Description
  `protected static final int`
  `DEFAULT_AUTHORIZATION_TIMEOUT`
  Default safety timeout for authorization flows, which involve a user tapping through a sheet.
  `protected static final int`
  `DEFAULT_OPERATION_TIMEOUT`
  Default per-operation safety timeout for reads and writes.

- ## Constructor Summary

  Constructors
  Modifier
  Constructor
  Description
  `protected`
  `HealthStore()`
  Ports construct subclasses.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `final AsyncResource<List<AggregateResult>>`
  `aggregate(AggregateQuery query)`
  Summarizes data into time buckets.
  `protected final void`
  `aggregateByReadingSamples(AggregateQuery query, long[] boundaries, AsyncResource<List<AggregateResult>> out)`
  The fallback aggregation: read every requested type over the query's range, then bucket the samples locally.
  `protected final List<AggregateResult>`
  `aggregateSamples(AggregateQuery query, long[] boundaries, List<HealthSample> samples)`
  Aggregates raw samples into the query's buckets.
  `protected final long[]`
  `bucketBoundaries(AggregateQuery query)`
  Computes the bucket boundaries for a query, as `n+1` timestamps bounding `n` buckets.
  `final AsyncResource<Integer>`
  `delete(HealthDeleteRequest request)`
  Deletes samples this app wrote.
  `protected void`
  `doAggregate(AggregateQuery query, long[] boundaries, AsyncResource<List<AggregateResult>> out)`
  Computes bucketed aggregates.
  `protected void`
  `doDelete(HealthDeleteRequest request, AsyncResource<Integer> out)`
  Deletes samples matching an already-validated request.
  `protected void`
  `doDrainChanges(List<HealthSubscription> subscriptions, AsyncResource<Integer> out)`
  Polls for pending changes and delivers them through `fireChanges(HealthChangeBatch)`, resolving with the number of batches delivered.
  `protected void`
  `doGetAuthorizationRequestStatus(List<HealthAccess> access, AsyncResource<HealthRequestStatus> out)`
  Reports whether the authorization sheet would show anything.
  `protected void`
  `doReadSamples(SampleQuery query, AsyncResource<SamplePage> out)`
  Reads one page of raw platform samples.
  `protected void`
  `doRequestAuthorization(List<HealthAccess> access, AsyncResource<Boolean> out)`
  Presents the platform authorization UI.
  `protected void`
  `doSubscribe(SubscriptionRequest request, HealthAnchor anchor)`
  Registers a platform observer, resuming from `anchor` when one was persisted.
  `protected void`
  `doUnsubscribe(String subscriptionId)`
  Tears down a platform observer.
  `protected void`
  `doWrite(List<HealthSample> samples, AsyncResource<HealthWriteResult> out)`
  Writes a chunk no larger than `getMaxWriteBatchSize()`, already validated and converted into `getPreferredWriteUnit(HealthDataType)`.
  `final AsyncResource<Integer>`
  `drainChanges()`
  Immediately drains pending changes for every active subscription, resolving with the number of batches delivered.
  `protected final void`
  `fireChanges(HealthChangeBatch batch)`
  Delivers a batch to whichever listener the subscription registered, on the EDT.
  `final AsyncResource<HealthRequestStatus>`
  `getAuthorizationRequestStatus(HealthAccess... access)`
  Whether presenting the authorization sheet would show the user anything -- see `HealthRequestStatus`.
  `int`
  `getMaxWriteBatchSize()`
  The largest number of samples one platform write call accepts.
  `protected final int`
  `getOperationTimeout()`
  The current per-operation safety timeout.
  `HealthUnit`
  `getPreferredWriteUnit(HealthDataType type)`
  The unit this platform prefers to receive `type` in.
  `HealthAuthorizationStatus`
  `getReadAuthorizationStatus(HealthDataType type)`
  This app's read authorization for `type`.
  `final List<HealthSubscription>`
  `getSubscriptions()`
  Every currently registered subscription.
  `List<AggregateMetric>`
  `getSupportedMetrics(HealthDataType type)`
  The metrics this platform can compute natively for `type`.
  `List<HealthDataType>`
  `getSupportedTypes()`
  The types this platform can read.
  `HealthAuthorizationStatus`
  `getWriteAuthorizationStatus(HealthDataType type)`
  This app's write authorization for `type`.
  `final AsyncResource<Boolean>`
  `hasAnyData(HealthDataType type, HealthTimeRange range)`
  Runs a bounded query and reports whether any sample came back.
  `boolean`
  `isBackgroundDeliverySupported()`
  Whether this platform can deliver changes without the app asking.
  `boolean`
  `isDeletable(HealthDataType type)`
  Whether samples of `type` can be deleted.
  `boolean`
  `isPushDelivery()`
  Whether the OS wakes the app on new data -- see `HealthSubscription.isPushDelivery()`.
  `boolean`
  `isSupported()`
  `true` when this store can do anything at all.
  `boolean`
  `isTypeSupported(HealthDataType type)`
  Whether this platform exposes `type`.
  `boolean`
  `isWritable(HealthDataType type)`
  Whether this app may write `type`.
  `protected final HealthAnchor`
  `loadAnchor(String subscriptionId)`
  The persisted cursor for a subscription, or null to start fresh.
  `final AsyncResource<SamplePage>`
  `readSamplePage(SampleQuery query)`
  Reads a single page of samples.
  `final AsyncResource<List<HealthSample>>`
  `readSamples(SampleQuery query)`
  Reads samples, following paging until the query's limit is reached or the data runs out.
  `final AsyncResource<Boolean>`
  `requestAuthorization(HealthAccess... access)`
  Presents the platform authorization UI for the requested access.
  `protected final void`
  `restoreSubscriptions()`
  Re-registers every subscription persisted by a previous launch.
  `static void`
  `setBackgroundListenerFactory(HealthBackgroundListenerFactory factory)`
  Installs the build-generated factory that constructs background listeners after a process relaunch.
  `protected final void`
  `setOperationTimeout(int millis)`
  Sets the per-operation safety timeout in milliseconds.
  `protected final void`
  `storeAnchor(String subscriptionId, HealthAnchor anchor)`
  Persists a cursor.
  `final HealthSubscription`
  `subscribe(SubscriptionRequest request, HealthChangeListener listener)`
  Subscribes with an in-memory listener, dropped when the process ends.
  `final HealthSubscription`
  `subscribe(SubscriptionRequest request, Class backgroundListenerClass)`
  Subscribes with a listener that survives the app being killed.
  `final void`
  `unsubscribe(String subscriptionId)`
  Cancels a subscription and discards its persisted cursor.
  `final AsyncResource<HealthWriteResult>`
  `write(HealthSample sample)`
  Writes one sample.
  `final AsyncResource<HealthWriteResult>`
  `write(List<HealthSample> samples)`
  Writes several samples, chunking to `getMaxWriteBatchSize()`.

  ### Methods inherited from class Object

  `clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- ## Field Details

  - ### DEFAULT_OPERATION_TIMEOUT

    protected static final int DEFAULT_OPERATION_TIMEOUT
    Default per-operation safety timeout for reads and writes.
    See Also:
      - Constant Field Values

  - ### DEFAULT_AUTHORIZATION_TIMEOUT

    protected static final int DEFAULT_AUTHORIZATION_TIMEOUT
    Default safety timeout for authorization flows, which involve a user tapping through a sheet.
    See Also:
      - Constant Field Values

- ## Constructor Details

  - ### HealthStore

    protected HealthStore()
    Ports construct subclasses. Application code obtains the active store from `Health.getStore()`.

- ## Method Details

  - ### isSupported

    public boolean isSupported()
    `true` when this store can do anything at all. `false` on the fallback base class.

  - ### isTypeSupported

    public boolean isTypeSupported(HealthDataType type)
    Whether this platform exposes `type`. Even a fully supported platform covers a subset of `HealthDataType.values()`.

  - ### getSupportedTypes

    public List<HealthDataType> getSupportedTypes()
    The types this platform can read. Empty on the fallback.

  - ### isWritable

    public boolean isWritable(HealthDataType type)
    Whether this app may write `type`. Some types are read-only on some platforms -- derived metrics the OS computes itself.

  - ### isDeletable

    public boolean isDeletable(HealthDataType type)
    Whether samples of `type` can be deleted. Both platforms restrict deletion to data this app wrote.

  - ### getSupportedMetrics

    public List<AggregateMetric> getSupportedMetrics(HealthDataType type)
    The metrics this platform can compute natively for `type`. Metrics outside this set are computed in shared code from raw samples, which is slower but portable.

  - ### isBackgroundDeliverySupported

    public boolean isBackgroundDeliverySupported()
    Whether this platform can deliver changes without the app asking.

  - ### isPushDelivery

    public boolean isPushDelivery()
    Whether the OS wakes the app on new data -- see `HealthSubscription.isPushDelivery()`.

  - ### getMaxWriteBatchSize

    public int getMaxWriteBatchSize()
    The largest number of samples one platform write call accepts. Health Connect caps this at 1000; the base class chunks larger writes automatically.

  - ### getPreferredWriteUnit

    public HealthUnit getPreferredWriteUnit(HealthDataType type)
    The unit this platform prefers to receive `type` in. The base class converts before calling `doWrite(List,AsyncResource)`, so ports never do unit arithmetic.

  - ### requestAuthorization

    public final AsyncResource<Boolean> requestAuthorization(HealthAccess... access)

    Presents the platform authorization UI for the requested access.

    Resolving `true` means the flow completed, not that access was granted. On iOS the sheet completes identically whether the user enabled every switch or none of them, and HealthKit will not say which. Treat `true` as "the user has now been asked".

    Only Android distinguishes an explicit dismissal, which fails with `HealthError.USER_CANCELED`.

  - ### getWriteAuthorizationStatus

    public HealthAuthorizationStatus getWriteAuthorizationStatus(HealthDataType type)
    This app's write authorization for `type`. Truthful on both platforms.

  - ### getReadAuthorizationStatus

    public HealthAuthorizationStatus getReadAuthorizationStatus(HealthDataType type)

    This app's read authorization for `type`.

    Returns `HealthAuthorizationStatus.UNKNOWN` on iOS in every case. HealthKit deliberately refuses to disclose read authorization, because an app that could tell the difference between "denied" and "no data" could infer that a user is hiding a pregnancy or a prescription. Android answers truthfully, because its read permissions are ordinary runtime grants.

    The Android port does not paper over the difference by pretending iOS behaves the same way, and neither should your UI: never tell a user "you denied access" on the strength of this. Say "no data available" and offer `Health.openHealthSettings()`.

    See `hasAnyData(HealthDataType,HealthTimeRange)` for the only honest probe.

  - ### getAuthorizationRequestStatus

    public final AsyncResource<HealthRequestStatus> getAuthorizationRequestStatus(HealthAccess... access)
    Whether presenting the authorization sheet would show the user anything -- see `HealthRequestStatus`. Useful for deciding whether to show your own explainer screen first.

  - ### hasAnyData

    public final AsyncResource<Boolean> hasAnyData(HealthDataType type, HealthTimeRange range)

    Runs a bounded query and reports whether any sample came back.

    This measures data presence, not permission, and the name says so on purpose. On iOS a `false` means "denied, or genuinely no data" and the two are indistinguishable -- that is a platform privacy guarantee, not a gap in this API.

    Note also that on iOS your own writes remain readable to you even when read access was denied, so `true` does not prove you can see other apps' data.

  - ### readSamples

    public final AsyncResource<List<HealthSample>> readSamples(SampleQuery query)

    Reads samples, following paging until the query's limit is reached or the data runs out.

    Results are normalized: values arrive in the query's unit or the type's canonical unit, and series are flattened unless `SampleQuery.setFlattenSeries(boolean)` says otherwise.

  - ### readSamplePage

    public final AsyncResource<SamplePage> readSamplePage(SampleQuery query)
    Reads a single page of samples. Prefer this over `readSamples(SampleQuery)` for high-frequency types, so peak memory stays bounded regardless of how much history exists.

  - ### aggregate

    public final AsyncResource<List<AggregateResult>> aggregate(AggregateQuery query)

    Summarizes data into time buckets.

    Returns one `AggregateResult` per bucket, in chronological order, including buckets that held no data -- those come back empty rather than being omitted, so a chart can render a gap where a gap belongs. Read the double-counting warning on `AggregateQuery` before trusting a cross-platform total.

  - ### aggregateSamples

    protected final List<AggregateResult> aggregateSamples(AggregateQuery query, long[] boundaries, List<HealthSample> samples)

    Aggregates raw samples into the query's buckets.

    This is the one implementation of the arithmetic. Ports that have a native aggregation API can override `doAggregate(AggregateQuery, long[], AsyncResource)` and use it; ports that do not get this for free, which is what stops a total computed on iOS from disagreeing with the same total computed on Android because two implementations rounded differently.

  - ### bucketBoundaries

    protected final long[] bucketBoundaries(AggregateQuery query)

    Computes the bucket boundaries for a query, as `n+1` timestamps bounding `n` buckets.

    Exposed to ports because both platforms want the boundaries up front, and because getting daylight-saving right exactly once is better than getting it wrong twice.

  - ### write

    public final AsyncResource<HealthWriteResult> write(HealthSample sample)
    Writes one sample.

  - ### write

    public final AsyncResource<HealthWriteResult> write(List<HealthSample> samples)

    Writes several samples, chunking to `getMaxWriteBatchSize()`.

    Rejects before touching the platform: types this app cannot write, instantaneous samples of interval-only types such as `HealthDataType.STEPS`, and quantities whose unit measures the wrong dimension. Catching these here turns what would be an opaque platform exception into a message that names the offending sample.

  - ### delete

    public final AsyncResource<Integer> delete(HealthDeleteRequest request)
    Deletes samples this app wrote. See `HealthDeleteRequest`.

  - ### subscribe

    public final HealthSubscription subscribe(SubscriptionRequest request, Class backgroundListenerClass)

    Subscribes with a listener that survives the app being killed.

    `backgroundListenerClass` must be a public top-level class with a public no-argument constructor implementing `HealthBackgroundListener` -- see that interface for the full contract.

    ###### Throws

    - `IllegalArgumentException`: if the class is null or does not implement `HealthBackgroundListener`. This is checked eagerly, because the alternative is discovering the mistake weeks later when a background relaunch silently does nothing.

  - ### subscribe

    public final HealthSubscription subscribe(SubscriptionRequest request, HealthChangeListener listener)
    Subscribes with an in-memory listener, dropped when the process ends. Use the `Class` overload if you need delivery after the app has been killed.

  - ### unsubscribe

    public final void unsubscribe(String subscriptionId)
    Cancels a subscription and discards its persisted cursor. Idempotent.

  - ### getSubscriptions

    public final List<HealthSubscription> getSubscriptions()
    Every currently registered subscription.

  - ### drainChanges

    public final AsyncResource<Integer> drainChanges()

    Immediately drains pending changes for every active subscription, resolving with the number of batches delivered.

    Called automatically at app start and when the app returns to the foreground. On Android you should also call it from your background-fetch handler -- Health Connect never wakes the app on its own, so nothing else will notice new data while your app is closed.

  - ### fireChanges

    protected final void fireChanges(HealthChangeBatch batch)

    Delivers a batch to whichever listener the subscription registered, on the EDT.

    The cursor is persisted only after the listener returns. That ordering means a crash inside a listener costs one redelivered batch rather than losing the data permanently -- the opposite ordering would advance past data the app never actually processed.

  - ### setBackgroundListenerFactory

    public static void setBackgroundListenerFactory(HealthBackgroundListenerFactory factory)

    Installs the build-generated factory that constructs background listeners after a process relaunch.

    Called by code the build server injects into app startup, in the same way the Android port's native bridges are registered. There is deliberately no reflective fallback -- see `HealthBackgroundListenerFactory` for why resolving a class by name is the wrong mechanism on these targets.

  - ### loadAnchor

    protected final HealthAnchor loadAnchor(String subscriptionId)
    The persisted cursor for a subscription, or null to start fresh.

  - ### storeAnchor

    protected final void storeAnchor(String subscriptionId, HealthAnchor anchor)
    Persists a cursor. Called by `fireChanges(HealthChangeBatch)` after successful delivery; ports rarely need it directly.

  - ### restoreSubscriptions

    protected final void restoreSubscriptions()

    Re-registers every subscription persisted by a previous launch.

    This is what makes a subscription survive the process being killed: without it, an app relaunched into the background has no idea it was ever watching anything.

    It runs on its own, the first time anything touches subscriptions -- see `ensureSubscriptionsRestored()`. It used to be documented as something ports had to call from their constructor, which no port did, so persisted subscriptions were silently never restored on any platform. A constructor could not have called it safely anyway: it dispatches to `doSubscribe`, which a subclass has not finished initialising at that point.

  - ### setOperationTimeout

    protected final void setOperationTimeout(int millis)
    Sets the per-operation safety timeout in milliseconds. Ports use it so that a platform callback that never arrives surfaces as `HealthError.TIMEOUT` instead of an operation that hangs forever.

  - ### getOperationTimeout

    protected final int getOperationTimeout()
    The current per-operation safety timeout.

  - ### doReadSamples

    protected void doReadSamples(SampleQuery query, AsyncResource<SamplePage> out)
    Reads one page of raw platform samples. Input is already validated; units and series flattening are handled by the caller.

  - ### doAggregate

    protected void doAggregate(AggregateQuery query, long[] boundaries, AsyncResource<List<AggregateResult>> out)

    Computes bucketed aggregates. `boundaries` holds `n+1` timestamps bounding `n` buckets, already daylight-saving correct.

    Unlike the rest of the port SPI this does not default to NOT_SUPPORTED. Neither HealthKit nor Health Connect gives us an aggregation we can use directly for every metric this API exposes, so the default reads the raw samples and runs the shared arithmetic in `aggregateSamples(AggregateQuery, long[], List)`. A port only overrides this when its native aggregation is genuinely better -- and if it does, it owes the same answers.

  - ### aggregateByReadingSamples

    protected final void aggregateByReadingSamples(AggregateQuery query, long[] boundaries, AsyncResource<List<AggregateResult>> out)

    The fallback aggregation: read every requested type over the query's range, then bucket the samples locally.

    Reads one type at a time because HealthKit accepts only one type per query, and it is the narrower contract of the two.

  - ### doWrite

    protected void doWrite(List<HealthSample> samples, AsyncResource<HealthWriteResult> out)
    Writes a chunk no larger than `getMaxWriteBatchSize()`, already validated and converted into `getPreferredWriteUnit(HealthDataType)`.

  - ### doDelete

    protected void doDelete(HealthDeleteRequest request, AsyncResource<Integer> out)
    Deletes samples matching an already-validated request.

  - ### doRequestAuthorization

    protected void doRequestAuthorization(List<HealthAccess> access, AsyncResource<Boolean> out)
    Presents the platform authorization UI.

  - ### doGetAuthorizationRequestStatus

    protected void doGetAuthorizationRequestStatus(List<HealthAccess> access, AsyncResource<HealthRequestStatus> out)
    Reports whether the authorization sheet would show anything.

  - ### doSubscribe

    protected void doSubscribe(SubscriptionRequest request, HealthAnchor anchor)
    Registers a platform observer, resuming from `anchor` when one was persisted. Called on registration and again on `restoreSubscriptions()`.

  - ### doUnsubscribe

    protected void doUnsubscribe(String subscriptionId)
    Tears down a platform observer.

  - ### doDrainChanges

    protected void doDrainChanges(List<HealthSubscription> subscriptions, AsyncResource<Integer> out)
    Polls for pending changes and delivers them through `fireChanges(HealthChangeBatch)`, resolving with the number of batches delivered.