> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Flutter SDK: Orbit quickstart for Dart

> Official Orbit Flutter SDK quickstart — video rooms, in-app chat, and Segment-spec CDP analytics for Flutter and Dart (iOS, Android, web, desktop).

# 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](/sdks/node) or the REST API. See [Client / mobile SDKs (different scope, by design)](/sdks) on the SDK index.

<Note>
  **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.
</Note>

## Installation (pub)

```bash theme={null}
dart pub add orbit_flutter
```

Pre-publish, pin the source directly in `pubspec.yaml`:

```yaml theme={null}
dependencies:
  orbit_flutter: ^0.3.0
```

## 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.

```dart theme={null}
import 'package:orbit_flutter/orbit_flutter.dart';

// `token` + `serverUrl` come from your backend's call to
// POST /api/v1/video/rooms/:id/join.
final room = OrbitVideoRoom(
  token: token,
  serverUrl: serverUrl,
  engine: myOrbitMediaEngine,
);

room.onTrackSubscribed((track, participant) => render(track, participant));
room.onCaption((caption) => showCaption(caption));
await room.join();

await room.joinBreakout('support-tier-2', token: breakoutToken);
await room.returnToMainRoom();
await room.leave();
```

## 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.

```dart theme={null}
import 'package:orbit_flutter/orbit_chat.dart';

final chat = OrbitChatClient(
  publicKey: 'dv_live_pk_…',   // publishable key ONLY — never a secret key
  appInfo: 'acme-flutter/2.3.1',
);

chat.onMessage((m) => print('agent: ${m.body}'));
chat.onMessages((all) => render(all));
chat.onTyping((isTyping) => setTyping(isTyping));

await chat.connect();
await chat.sendMessage('Hi, I need help with my order');

// Upload a file attachment (10 MB client-side guard):
// await chat.sendAttachment(OrbitChatAttachment(
//   bytes: fileBytes, name: 'photo.jpg', mimeType: 'image/jpeg'));
```

Pass an `OrbitChatStorage` implementation to keep the conversation alive across app restarts — otherwise it lives only for the process lifetime. Call `chat.dispose()` during teardown to release the underlying HTTP resources.

## 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.

```dart theme={null}
import 'package:orbit_flutter/orbit_cdp.dart';

final analytics = OrbitCdpAnalytics(
  apiUrl: 'https://api.orbit.devotel.io',
  ingestId: 'ing_xxx',       // issued by your backend
  ingestSecret: '…',         // injected per-session by your backend
);

await analytics.track(
  userId: 'u_42',
  event: 'Order Completed',
  properties: {'revenue': 99.99, 'currency': 'USD'},
);
await analytics.identify(userId: 'u_42', traits: {'plan': 'pro'});
await analytics.screen(userId: 'u_42', name: 'Checkout');

// Batch up to 200 events per envelope:
await analytics.batch([
  CdpEvent.screen(userId: 'u_42', name: 'Home'),
  CdpEvent.track(userId: 'u_42', event: 'Button Tapped'),
]);

analytics.close();
```

## Error handling

* Construction misuse fails fast: an empty or server-side secret key (`dv_live_sk_…` / `dv_test_sk_…`) passed to `OrbitChatClient` throws `ArgumentError`, because nothing bundled in a distributed binary stays secret.
* CDP and chat API failures surface as typed exceptions — `OrbitCdpException` / `OrbitChatApiError` — carrying the failure context; an oversized attachment also throws `ArgumentError` before any bytes leave the device. Subscribe to `chat.onError` for non-fatal errors from the background stream.

```dart theme={null}
try {
  await analytics.track(userId: 'u_42', event: 'Order Completed');
} on OrbitCdpException catch (err) {
  // surface the failed event; deciding to retry is your policy
  reportError(err);
}
```

Only the **publishable key** (`dv_live_pk_…` / `dv_test_pk_…`) may ship inside the app. The CDP ingest secret is session-scoped and must come from your backend, never a compiled-in constant — the video room surface needs no key in the app at all.
