Skip to main content
The session SDK has two error surfaces: in-stream error events that end a running session, and exceptions thrown synchronously for API misuse. The error codes are the same in Dart, Kotlin, and Swift.

SessionErrorCode

Every SDK has a SessionErrorCode enum whose values serialize to the same snake_case strings: Parsing a wire string: Dart SessionErrorCode.fromString(value) throws ArgumentError for an unknown value; Swift uses SessionErrorCode(rawValue:), which returns nil; Kotlin’s SessionEvent.fromMap maps an unknown error_code to INVALID_STATE. The wire format is JSON-style maps (see Watch Protocol); there is no protobuf schema.

In-stream error events

An error event is terminal: no summary follows it. The Swift AsyncStream and the Dart stream finish after it; the Kotlin SharedFlow does not complete, so collectors stop on it themselves. message is human-readable and platform-specific. Log it, but don’t parse it for control flow β€” only code is stable.

Who emits them

Wire key

Kotlin toMap() and Swift toDictionary() put the code under error_code. Dart SessionError.toJson() writes it under code, and SessionError.fromJson() reads code, so the Dart decoder does not accept a session_error map that only carries error_code.

Recovery paths

Thrown exceptions

Dart

These are thrown directly from the call, not delivered on the stream. stopSession with an unknown id does not throw. ingestHsiMetrics and dispose never throw.

Kotlin

SessionError is a sealed Exception with subclasses PermissionDenied, SensorUnavailable, InvalidState, LowBattery, and OsTerminated, each with a code. Providers throw SessionError.SensorUnavailable from startStreaming; SynheartSession turns that into a SessionErrorEvent.

Swift

SessionError is an enum (Error) with cases permissionDenied(String), sensorUnavailable(String), invalidState(String), lowBattery(String), and osTerminated(String); code returns the SessionErrorCode.

Distinguishing the two surfaces

Channel error swallowing

In Flutter, WatchBiosignalProvider treats event-channel errors as non-fatal, so a brief disconnect does not end the session. Apps that need strict reachability must poll getWatchStatus() and stop the session themselves.

Logging

The Dart SDK only prints from an assert-guarded block (a failed watch stop command), so release builds print nothing. The Kotlin WatchSessionRelay and HealthConnectBiosignalProvider log warnings with android.util.Log. Hosts that need structured logging should log events at the call site.