Architecture
Health Connector SDK is a facade over two native health stores. Understanding the layers explains why some things are uniform and others are explicitly not.
The layers
Core defines the vocabulary: record models, measurement units, permission types, exceptions, and runtime platform requirements. It contains no platform code, which is why the same WeightRecord means the same thing everywhere.
Platform adapters translate that vocabulary into native calls. Each owns its Pigeon-generated channel and a set of mappers — toDto() on the way down, toDomain() on the way back.
The facade is what you import. HealthConnector.create() detects the platform and returns the matching client; everything after that is delegation.
Why a facade rather than a common denominator
A "lowest common denominator" API would have to drop updateRecord() entirely, because HealthKit cannot do it. Health Connector SDK instead exposes the full union of capabilities and checks platform-specific parts at runtime:
Future<void> updateRecord(HealthRecord record);This call has no iOS implementation and throws UnsupportedOperationException there. Data types and features expose healthPlatformRequirements so callers can check platform and version support before executing an operation.
The same principle governs data types: a type that only Health Connect models is still available, reports a Health Connect-only runtime requirement, and throws on iOS rather than being omitted from the SDK.
What crosses the channel
Requests do not travel as records. They travel as DTOs generated by Pigeon, so the boundary is type-checked on both sides at build time rather than being a Map<String, dynamic> contract you have to keep in sync by hand.
StepsRecord (your code)
→ StepsRecordDto (toDto(), Dart side)
→ StepsRecordDto (Pigeon-generated Kotlin/Swift)
→ StepsRecord (native record type)A practical consequence: unit conversions happen in Dart, before the channel. Mass.pounds(155) becomes the platform's expected unit during mapping, so a unit mistake is impossible to introduce at the boundary.
Native logs come back to Dart
Diagnostics from the Kotlin and Swift layers are forwarded through the same channel into your Dart log processors. There is one control plane for SDK output rather than three — Dart, Logcat, and Console — which is what makes the zero-logging default meaningful. See Configure logging.
What this means for your code
- Import only
package:health_connector/health_connector.dart. The platform packages are transitive. - Do not branch on platform for ordinary reads, writes, and aggregates — they are uniform.
- Resolve an API's
healthPlatformRequirementswithgetSupportStatusFor(), and retain anUnsupportedOperationExceptionfallback. - Never call anything marked
@internalUse; those factories exist for the adapters.