Skip to main content

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)

The package pins 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

  1. Construct with the publishable key, a storage adapter, and an appInfo tag — misuse fails here at the constructor, not on the first network call.
  2. Subscribe every on… handler you need (message, typing, connection, error). Each returns an unsubscribe function; subscribe before connect() so you never miss the initial history reload.
  3. 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.
  4. Send — sendMessage / sendAttachment run with optimistic local echo, so your UI updates before the server acknowledges.
  5. 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. Each on… subscription returns an unsubscribe function.
Send an attachment (10 MB client-side guard trips before any bytes leave the device):
The first outbound send also carries an 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:
The client stores the active conversation id and the anonymous visitor id under 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-spec track / 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.
A 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.
The engine contract is six methods — 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 against POST /api/v1/push/device-tokens, run with a secret key via the Node SDK or plain HTTP:
No SDK in the backend? The same call over HTTP:
Re-registering an existing token is an upsert — call it on every platform token rollover and last-seen is bumped, never duplicated. Delivery-side mechanics (categories, rich media, scheduling) live in the push channel reference.

Runnable examples

The package’s examples/ 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.