# Flutter

Flutter bridge over the native Android and iOS chat SDKs; the native chat screen is presented, so there is no Dart-side UI to compose. Flutter 3.3+, iOS 14+, Android minSdk 24 with JDK 17 and `mavenCentral()` in your Gradle repositories.

## Install and quick start

```bash
flutter pub add newinstance_chat:^0.5.0
```

Your backend mints a customer token (see [Session tokens (REST API)](/session-tokens-rest-api)) and hands it to the app:

```dart
final chat = NewinstanceChat(NewinstanceChatConfig(customerToken: tokenFromYourBackend));
await chat.ready;
await chat.initialize();
await chat.openChat();
```

`NewinstanceChatConfig(apiKey?, customerToken?, baseUrl?, transport?, initialMessage?, theme?)` takes exactly one of `customerToken` or `apiKey` (`keyId:secret`); anything else makes `ready` reject with `E_CONFIG`. The `baseUrl` override is honoured by debug builds only. `ChatUser(customerName required, customerEmail?, customerId?)`. `ready` resolves once native configuration completes.

## Auth modes

**Customer token mode (recommended)** is shown above: the token is the identity and `setUser` fields are not sent. When a fresh token is due, swap it in without destroying the SDK:

```dart
await chat.setUserToken(freshTokenFromYourBackend);
```

**Key mode (trusted internal apps only).** This ships your secret inside the app, so never use it in an app distributed to the public.

```dart
final chat = NewinstanceChat(NewinstanceChatConfig(apiKey: 'sk_live_abc123:YOUR_SECRET_KEY'));
await chat.ready;
await chat.setUser(const ChatUser(customerName: 'Ada', customerEmail: 'ada@example.com', customerId: 'usr_123'));
await chat.initialize();
```

`setUserToken` throws `E_CONFIG` in key mode. See [Authentication and identity](/authentication-and-identity).

## Local theme

```dart
NewinstanceChatConfig(
  customerToken: tokenFromYourBackend,
  theme: ChatThemeOverride(mode: 'dark', sentBubble: '#16A34A'),
);
```

Every `ChatThemeOverride` field is optional and merged token by token, ranked below the merchant's dashboard theme and above the built-in palette. Unset tokens are omitted from the platform-channel payload entirely. Full rules in [Theming](/theming).

## API and events

Methods: `setUser`, `setUserToken`, `initialize`, `openChat`, `closeChat`, `sendMessage`, `retryMessage`, `attachFile(AttachmentInput(uri, name, mimeType))`, `destroy`. Handoff, typing and attachment removal are driven by the native screen, not the Dart surface.

Streams: `onMessageReceived`, `onMessageSent`, `onAgentTypingChanged`, `onConnectionStateChanged` (`NcConnectionState`: idle, connecting, connected, disconnected, offline), `onUnreadCountChanged`, `onError`. `ChatMessage` carries seq, sentAt, senderName and totalMessages.

## The error stream

`onError` is the channel for "why is my chat not working". Nothing on it reaches the chat interface, and it never carries the API key, the identity token, or customer data.

```dart
chat.onError.listen((NewinstanceChatError e) {
  if (e.code == NcErrorCode.invalidIdentityToken) {
    refreshChatToken();
  } else if (!e.recoverable) {
    reportToYourMonitoring(e.code, e.message);
  }
});
```

`NewinstanceChatError` carries `code` (from `NcErrorCode`, the vocabulary shared with every other chat SDK), the broad `type` (`NcErrorType`: network, validation, auth, system), a developer-facing `message`, and `recoverable`. `code` is null only when talking to a native SDK older than the release that introduced coded errors. The full code list is in **Authentication and identity -> Diagnosing problems**.

## Gotchas

- `AttachmentInput.uri` is a `file://` URI on iOS and `content://` or `file://` on Android.
- `theme` and coded errors require 0.3.0 of this package and of the underlying native SDKs. The two exclusive auth modes, `customerToken` in the config and the reworked `setUserToken` require 0.4.0.
- Steps from your connected MCP servers (forms and secure pages) require 0.5.0 of this package and of the underlying native SDKs; see [MCP servers](/mcp-servers).
- Multiple `NewinstanceChat` instances reconfigure one native singleton; the last config wins.
- After `destroy()`, construct a new instance to chat again.
