Skip to content

Annotations

The SDK uses annotations to state platform support, OS-version floors, and usage constraints directly on the API. Reading them correctly is how you avoid shipping a call that throws on half your users' devices.

The vocabulary

AnnotationMeansWhat to do
@supportedOnHealthConnectAndroid Health Connect onlyCheck HealthConnector.healthPlatform, or catch UnsupportedOperationException
@supportedOnAppleHealthiOS HealthKit onlySame
@supportedOnAppleHealthIOS16PlusiOS 16.0+ onlyCheck platform and OS version; throws below iOS 16
@supportedOnAppleHealthIOS17PlusiOS 17.0+ onlyThrows below iOS 17
@supportedOnAppleHealthIOS18PlusiOS 18.0+ onlyThrows below iOS 18
@supportedOnHealthConnectSdkExtension21Health Connect SDK Extension 21+Runtime device check — see below
@readOnlySystem-calculated metricUse readRecords() or aggregate() only; writing throws
@internalUseNot part of the public APIDo not call from application code

Annotations combine

When several appear on one declaration, every constraint applies at once.

Worked example

dart
@supportedOnAppleHealthIOS16Plus
@readOnly
final class InfrequentMenstrualCycleEventRecord extends IntervalHealthRecord {
  @internalUse
  factory InfrequentMenstrualCycleEventRecord.internal({...}) {...}
}

Read that as three separate facts:

  • @supportedOnAppleHealthIOS16Plus — iOS 16 or later only. Android and iOS 15 throw UnsupportedOperationException.
  • @readOnly — HealthKit calculates this; you can read it, never write or delete it.
  • @internalUse on the factory — that constructor exists for the SDK's own mappers.

Correct usage:

dart
final connector = await HealthConnector.create();

try {
  // Recommended: Read-only types support reads and aggregates.
  final now = DateTime.now();
  final response = await connector.readRecords(
    HealthDataType.infrequentMenstrualCycleEvent.readInTimeRange(
      startTime: now.subtract(const Duration(days: 1)),
      endTime: now,
    ),
  );

  // Avoid: Never call an @internalUse factory.
  // final record = InfrequentMenstrualCycleEventRecord.internal(...);

  // Avoid: Never write a @readOnly type — throws UnsupportedOperationException.
  // await connector.writeRecord(record);
} on UnsupportedOperationException catch (e) {
  print('This type requires iOS 16 or later: $e');
}

Lint rules are planned

A future health_connector_lint release will surface these annotations through the Dart analyzer, so the constraints become analyzer warnings instead of documentation you have to remember.

Exercise segment weight and SDK Extension 21

ExerciseSessionSegmentEvent.weight is annotated @supportedOnHealthConnectSdkExtension21. It maps to ExerciseSegment.weight, which only exists on devices whose Health Connect Mainline module is at SDK Extension 21 or higher.

ScenarioWriting a non-null weightValue when read
Android 14+ with Mainline Extension 21+Persisted normallyNon-null
Android 14+ without the Extension 21 updateThrows UnsupportedOperationExceptionnull
Android below 14Throws UnsupportedOperationExceptionnull
iOS HealthKitThrows UnsupportedOperationExceptionnull

Always guard the write:

dart
final segment = ExerciseSessionSegmentEvent(
  startTime: startTime,
  endTime: endTime,
  segmentType: ExerciseSegmentType.benchPress,
  repetitions: 10,
  weight: Mass.kilograms(80), // needs SDK Extension 21+ on Android
);

try {
  await connector.writeRecord(exerciseSession);
} on UnsupportedOperationException catch (e) {
  // Omit the weight, or tell the user their device cannot store it.
  print('Segment weight not supported on this device: $e');
}

This is a runtime check, not a compile-time one

compileSdkExtension 19 in your Gradle config satisfies the build. The Extension 21 requirement is checked on the device. The same app binary succeeds on one Android 14 phone and throws on another, depending on whether that phone received the Mainline update — so you cannot test this away on a single device.

Released under the Apache 2.0 License.