Skip to main content
The session SDK decouples the engine from data sources via two abstractions. This page covers the contracts, the bundled implementations, and how the engine uses them. Providers are constructor arguments of SynheartSession, not fields of SessionConfig:

BiosignalProvider

The heart-rate source. The engine starts it once per session and stops it when the session ends.

Contract (Dart)

Stream-based:

Contract (Kotlin and Swift)

Callback-based:
name is a debug identifier. The Dart live engine skips the subscription when isAvailable is false. The Kotlin and Swift engines do not check isAvailable; they call startStreaming, and if it throws, the session emits a sensor_unavailable error event and ends.

BiosignalSample

Dart:
Kotlin and Swift:
The Swift struct has the same fields (timestampMs: Int64, bpm, rrIntervalsMs, deviceId, source). source is required and is copied into raw biosignal_frame samples. Kotlin and Swift samples have no accelerometer or SpO₂ fields.

Engine consumption

The engine pushes each sample into a sliding window (see Lifecycle) and computes hr_mean_bpm from it. There is no provider-side rate limiting. RR intervals, accelerometer, and SpO₂ are not used to compute any metric. Kotlin and Swift copy RR intervals into raw biosignal_frame samples; the Dart live engine does not emit raw frames.

Bundled implementations

Seeded mocks are Dart-only. The Dart SDK has no BLE HRM, Health Connect, or HealthKit provider; implement BiosignalProvider over your source.

BehaviorProvider

The behavioral signal source. Pull-based: the engine calls currentSnapshot() at each frame and once more for the summary.

Contract

Kotlin (interface BehaviorProvider) and Swift (protocol BehaviorProvider) have the same three members. The engine skips the provider when isAvailable is false. A null snapshot means “nothing right now”; the engine omits the behavior field on that frame.

BehaviorSnapshot

The shape is field-for-field aligned with synheart-behavior’s BehaviorStats, so the two SDKs share a contract without an import dependency. Kotlin and Swift define the same fields. On a frame’s behavior map, keys are snake_case and only non-null fields appear; app_switches_per_minute and timestamp are always present.

Bundled implementations

The Dart SDK does not bundle a synheart_behavior wrapper; implement BehaviorProvider over your behavior source.

Integration with the engine

Both are optional. Kotlin and Swift default the biosignal provider to MockBiosignalProvider. In the Dart live mode, with no biosignal provider:
  • LiveSessionEngine still emits SessionStarted, frames with sample_count = 0 and hr_mean_bpm = 0.0, and a final SessionSummary.
  • This is useful for testing the framing logic itself.

Provider state machine (informal)

Providers must tolerate stopStreaming() being called more than once — the Kotlin and Swift engines call it on session end and again on dispose(). They should stop emitting after stopStreaming() returns.

Watch provider specifics

WatchBiosignalProvider (Dart):
  • BiosignalFrame events → each BiosignalSample is passed to the engine directly.
  • SessionFrame events → one sample synthesised from metrics.hr_mean_bpm (when it is above 0) with rrIntervalMs = 60000.0 / bpm.
  • SessionStarted, SessionSummary, SessionError → ignored (lifecycle, not biosignal data).
Channel errors are non-fatal: the provider swallows them so a disconnect does not end the session.

Authoring a provider

A custom BiosignalProvider should:
  • Allocate sensor resources (BLE GATT subscribe, HealthKit authorization, etc.) on startStreaming() and release them on stopStreaming().
  • Dart: emit samples on the Stream<BiosignalSample> returned from startStreaming().
  • Kotlin / Swift: call onSample for each sample, with a source tag that describes the real source. Throw SessionError.SensorUnavailable / .sensorUnavailable from startStreaming if the source cannot start.
  • Not block the calling thread — startStreaming is called synchronously from startSession.
A custom BehaviorProvider should:
  • Maintain its own internal accumulators.
  • Return a fresh BehaviorSnapshot on each currentSnapshot() call (or null when no recent activity).
  • Use the current epoch milliseconds for the snapshot timestamp.
  • Lifecycle — when the engine starts and stops providers.
  • Watch Protocol — the watch events WatchBiosignalProvider consumes.