Documentation Index
Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt
Use this file to discover all available pages before exploring further.
The session SDK carries two error surfaces: in-stream SessionError events that close a running stream, and synchronous exception classes thrown for API misuse before/during a Stream<SessionEvent> is established. They are aligned across Dart, Kotlin, and Swift.
SessionErrorCode (in-stream)
Wire enum from session.proto:
enum ErrorCode {
ERROR_UNSPECIFIED = 0;
ERROR_PERMISSION_DENIED = 1;
ERROR_SENSOR_UNAVAILABLE = 2;
ERROR_LOW_BATTERY = 3;
ERROR_OS_TERMINATED = 4;
ERROR_INVALID_STATE = 5;
}
SDK projection — wire string is the snake_case JSON value the platform layer ships:
Dart SessionErrorCode | Wire string | Cause |
|---|
permissionDenied | permission_denied | OS denied a sensor permission (HealthKit, Health Connect, BLE, etc.). |
sensorUnavailable | sensor_unavailable | No paired wearable; HR sensor missing or returning no data. |
lowBattery | low_battery | Watch battery dropped below the OS threshold for an active workout/exercise session. |
osTerminated | os_terminated | OS killed the foreground/exercise session (memory pressure, user backgrounded, etc.). |
invalidState | invalid_state | API misuse: StartSession while already active, unknown session_id, etc. |
SessionErrorCode.fromString(value) (all three SDKs) parses the wire string. Unknown values throw ArgumentError in Dart.
SessionError event
class SessionError extends SessionEvent {
final SessionErrorCode code;
final String message;
}
SessionError always closes the stream. After it fires, the same sessionId can be started again with a fresh SessionConfig.
message is human-readable and platform-specific. Hosts should log it but should not parse it for control flow — only code is stable.
Recovery paths
| Code | Recovery |
|---|
permission_denied | Prompt for the missing permission via the appropriate platform API; restart the session. The SDK does not retry. |
sensor_unavailable | Reconnect the wearable; verify getWatchStatus() reports paired = true, reachable = true, installed = true. Switch to a different BiosignalProvider if available. |
low_battery | User remediation. The SDK does not throttle or downsample. |
os_terminated | Resume the session with a new sessionId. The OS-killed session is gone; partial summaries are not recoverable from the SDK side. |
invalid_state | Programmer error. Inspect message and the call site. Common causes: starting two sessions with the same id, calling on a disposed SynheartSession. |
Dart synchronous exceptions
For misuse that doesn’t reach the engine, the Dart SDK throws:
class SynheartSessionError implements Exception { ... }
class SessionPermissionDeniedError extends SynheartSessionError { ... }
class SessionSensorUnavailableError extends SynheartSessionError { ... }
class SessionInvalidStateError extends SynheartSessionError { ... }
| Exception | When |
|---|
SessionInvalidStateError | startSession after dispose(). startSession with a sessionId already running. Constructor preconditions. |
SessionPermissionDeniedError | Permission probe at construction time (rare; most permission failures surface as SessionError events instead). |
SessionSensorUnavailableError | No usable provider at construction. |
These do not reach the event stream — they’re thrown directly from SynheartSession.startSession(...) and friends.
Distinguishing the two surfaces
| Situation | Surface |
|---|
You called startSession with a duplicate id | Dart SessionInvalidStateError thrown synchronously. |
You called startSession after dispose() | Dart SessionInvalidStateError thrown synchronously. |
| OS denied HealthKit at session start | SessionError(permissionDenied, ...) event on the stream. |
| Watch went out of range | (no error — channel errors are swallowed; the engine continues with empty buffers) |
| Watch ran out of battery mid-session | SessionError(lowBattery, ...) event on the stream. |
| User backgrounded the app and OS killed the workout | SessionError(osTerminated, ...) event on the stream. |
Channel error swallowing
The Watch event channel listener treats channel transport errors as non-fatal — dropped frames during a brief disconnect are preferable to forcing the host to handle a SessionError(osTerminated) for every Bluetooth blip. Apps that need strict reachability semantics must poll getWatchStatus() and stop the session manually on persistent disconnect.
Logging
The SDK does not write to platform log streams beyond assert-guarded print calls. Hosts that need structured logging should subscribe to the Stream<SessionEvent> and log at the call site.