Flutter SDK (Dart)
The Orbit Flutter SDK (orbit_flutter) brings three client surfaces to a single Dart codebase that targets iOS, Android, web, and desktop: video rooms (OrbitVideoRoom), in-app chat (OrbitChatClient — the same omnichannel conversation/inbox the web widget and native mobile SDKs use), and CDP analytics (OrbitCdpAnalytics — Segment-spec event ingestion over the same HMAC-signed ingest contract the web and Node SDKs use).
These are client surfaces: chat authenticates with a publishable key (dv_live_pk_…), video rooms need no key at all (the token is minted server-side by your backend), and the CDP client uses a backend-injected ingest id and secret. Nothing here wraps the server-side management resources (messaging, voice, contacts, campaigns, verify) — those require a secret key and belong in your backend, via the Node SDK or the REST API. See Client / mobile SDKs (different scope, by design) on the SDK index.
Pre-publish — source-only. The Flutter SDK is not yet on pub.dev —
dart pub add orbit_flutter fails today. Until first publish, depend on
the source from the monorepo (packages/sdk-flutter/) via a path or git
dependency.Scope matrix
The division is deliberate: no surface in this package accepts a secret key, so nothing in your shipped binary can be turned into a full-account credential by extraction. Server-side resources stay server-side.
Building the owned in-app channel (content cards / in-app messages) too? Its engagement events go through the same closed
InAppEventType contract the native mobile SDKs use — see InAppEventType: the in-app event enum.
Installation (pub)
crypto + http and supports Dart >=3.0.0 <4.0.0, covering Dart-3-based Flutter releases. The three surfaces are tree-shakable entry points — import orbit_chat.dart, orbit_cdp.dart, or the umbrella orbit_flutter.dart and the bundler keeps only what you use.
Integration order
- Construct with the publishable key, a storage adapter, and an
appInfotag — misuse fails here at the constructor, not on the first network call. - Subscribe every
on…handler you need (message, typing, connection, error). Each returns an unsubscribe function; subscribe beforeconnect()so you never miss the initial history reload. await chat.connect()— restores or creates the conversation, loads the last 50 messages, then opens the real-time stream. Safe to call once; repeated calls are no-ops.- Send —
sendMessage/sendAttachmentrun with optimistic local echo, so your UI updates before the server acknowledges. - Tear down —
chat.disconnect()stops the stream and keeps the conversation id;chat.dispose()releases the underlying HTTP resources. Hook widget dispose → client dispose.
In-app chat (OrbitChatClient)
The client is headless: it owns the conversation lifecycle, history, optimistic send, and the real-time inbound stream; your widget tree renders it. Eachon… subscription returns an unsubscribe function.
Idempotency-Key; pass your own idempotencyKey to pin one across your retry loop, or let the transport stamp a fresh key per call.
Storage adapter contract
OrbitChatStorage is three methods — implement it over shared_preferences, hive, or flutter_secure_storage:
orbit_chat_visitor_id-style keys. Skip the adapter and the conversation lives for the process lifetime only — every cold start lands a brand-new visitor.
CDP analytics (OrbitCdpAnalytics)
Stream Segment-spectrack / identify / screen / page / group / alias / batch events into the Orbit Customer Data Platform. HMAC request signing happens inside the SDK boundary; the ingest id and secret are injected per session by your backend, never compiled in.
batch call returns a per-event received/count ack so you can reconcile which events the server accepted.
Video rooms (OrbitVideoRoom)
Headless and engine-injected: your backend mints the room token server-side (POST /api/v1/video/rooms/:id/join) and hands the token plus server URL to the app. Wire a VideoRoomEngine over your WebRTC stack (for example livekit_client) — the package carries no hard WebRTC dependency, so you are never vendoring a second WebRTC build beside the one you already use.
connect, disconnect, setCameraEnabled, setMicrophoneEnabled, setScreenShareEnabled, and a localParticipant() accessor. The client owns all room state (participants, publish flags, captions, active breakout) and fans typed events to your listeners; the engine only moves media. computeGridColumns + resolveTileLabel ship alongside as pure helpers so your tile grid matches the web room’s layout exactly.
Push token onboarding (backend)
Registering the device’s APNs (iOS) or FCM (Android) token is a backend call againstPOST /api/v1/push/device-tokens, run with a secret key via the
Node SDK or plain HTTP:
Runnable examples
The package’sexamples/ directory holds self-contained end-to-end Dart files — each takes the publishable key from ORBIT_PUBLIC_KEY (dv_test_pk_… / dv_live_pk_…): chat (connect + send with OrbitChatApiError handling), and cdp_analytics (OrbitCdpAnalytics.identify + track, or the equivalent HMAC batch). Swap sandbox vs live by changing the env value — no code edits. See packages/sdk-flutter/examples/README.md for how to run each with dart run examples/<name>/example.dart.
Error taxonomy
Version notes
orbit_flutter 0.3.0 requires Dart >=3.0.0 <4.0.0 and Flutter on the Dart-3 toolchain; the only runtime dependencies are crypto ^3.0.0 and http ^1.0.0. The 0.x line is pre-publish, so minor releases may adjust surface details — the source pins (dart pub deps) make the current contract machine-checkable. Pre-publish safety line aside, the wire contract is the production conversations/ingest endpoints, not a sandbox.
Only the publishable key (dv_live_pk_… / dv_test_pk_…) may ship inside the app — the chat client refuses a secret key at construction. The CDP ingest secret is session-scoped and must come from your backend, never a compiled-in constant, and the video room surface needs no key in the app at all. Real-time updates use header-authenticated polling, so the key never lands in a URL log.