# Flutter SDK

A Dart package with no UI. It keeps the conversation — messages, typing,
connection — in a `ChangeNotifier`, and you draw it however your app looks.
Android, iOS and macOS, Flutter 3.22 or later; its only dependencies are
`http`, `shared_preferences` and `package_info_plus`.

## Setup

Create a publishable key that allows your app. A Flutter app has one origin
per platform: `ios-app://` plus its bundle id (iOS and macOS), and
`android-app://` plus its package name:

```bash
vatio keys create publishable --env live --label app \
  --origin ios-app://com.acme.app --origin android-app://com.acme.app
```

To add them to an existing key instead, use `vatio keys origins PREFIX URL...`
or **Settings → API keys** in the inbox.

Add the package from pub.dev:

```bash
flutter pub add vatio
```

Android release builds need the `INTERNET` permission in
`android/app/src/main/AndroidManifest.xml` (Flutter's template only grants it
to debug builds), and macOS needs `com.apple.security.network.client` in both
`.entitlements` files.

Then create a client with the `vatpub_` key as `token`. It is meant to ship inside the
app; a `vat_` developer token must never.

```dart
import 'package:vatio/vatio.dart';

final vatio = Vatio(workspace: 'acme', token: 'vatpub_REPLACE_ME');
final chat = VatioChat(vatio);
```

The SDK reads the bundle id or package name from the app itself, so the same
code sends the right origin on each platform.

## Using it

```dart
chat.messages        // List<VatioMessage>, oldest first: id, role, content, createdAt, isPending
chat.isTyping        // the agent is writing
chat.status          // idle, connecting, connected, reconnecting, closed

await chat.send('What does Acme sell?');
```

`VatioChat` notifies its listeners on every change, so a `ListenableBuilder`
(or Provider's and Riverpod's `ChangeNotifierProvider`) redraws as the reply
arrives. Creating a chat opens nothing: the conversation starts on the first
`send`, and the visitor's message is in `messages` at once, with `isPending`
set until the server confirms it. Call `chat.resume()` when the screen appears
to reopen the visitor's conversation, if they have one, and `chat.dispose()`
from the `dispose` of the widget or service that owns it.

```dart
ListenableBuilder(
  listenable: chat,
  builder: (context, _) => ListView(
    reverse: true,
    children: [
      if (chat.isTyping) const TypingDots(),
      for (final message in chat.messages.reversed) Bubble(message),
    ],
  ),
)
```

The [package README](https://github.com/vatio-ai/flutter#readme) has the
complete example: a text bar that switches to an Assistant tab and sends.
[An AI agent in your Flutter app](https://vatio.ai/docs/guides/ai-agent-in-your-flutter-app)
walks through a whole app, from the key to a signed-in user's private tools.

| Method | Purpose |
|---|---|
| `vatio.config()` | Agent name, avatar, accent color, greeting and suggestions |
| `vatio.conversations(visitorToken:)` | This visitor's conversations on this device, newest first |
| `chat.send(text, attachments:)` | Send a message, optionally with files; starts the conversation on first call |
| `chat.resume()` | Reopen the stored conversation; never starts a new one |
| `chat.open(conversation)` | Switch to a conversation from `conversations()` |
| `chat.startOver()` | Forget the stored conversation; server history is kept |
| `chat.rate(rating, comment:)` | Answer the feedback moment: `good`, `neutral`, `bad` |
| `chat.dismissFeedback()` | Decline the feedback moment |
| `chat.close()` | Disconnect; `resume()` or `send` reconnects |
| `chat.dispose()` | Disconnect for good |

`send` throws a `VatioException` (`code`, `message`, `status`) and removes the
pending message, so put the text back in your field. Failures outside a
`send` land in `chat.lastError`.

## Files and voice notes

Attach up to four `VatioUpload`s — images, PDFs, text files or audio, up to
8 MB each, as on the [web](https://vatio.ai/docs/sdk/reference#files-and-voice-notes). With files,
the text may be empty:

```dart
await chat.send('', attachments: [await VatioUpload.fromPath(photo.path)]);   // image_picker, file_picker
await chat.send('', attachments: [await VatioUpload.fromPath(recordingPath)]); // a recorder's .m4a
```

Once the server answers, the pending message is replaced by the stored one,
and each `VatioAttachment` has a `url`. A voice note is transcribed in the
background: the message reads "Audio" at first, and its `content` becomes the
transcript a few seconds later, in place; the agent answers after that. When
`isMediaLabel` is true, `content` is only a stand-in ("Image") — draw the
attachment instead. The example app has a composer with a photo picker.

## Visitor feedback

When a request looks resolved, Vatio offers a feedback moment, at most once
per conversation (see [the widget guide](https://vatio.ai/docs/channels/widget#visitor-feedback)).
`chat.feedback` holds it; show your own prompt while `isOpen`:

```dart
final feedback = chat.feedback;
if (feedback != null && feedback.isOpen) {
  return RatingCard(
    question: feedback.question ?? 'How did ${feedback.agentName} do?',
    onRate: (rating, comment) => chat.rate(rating, comment: comment),
  );
}
```

`question` is how the agent asks, written for that conversation, or `null`
when there is none. It goes back to `null` when the visitor writes again
without answering.

## Signed-in users

Pass a visitor token your backend signed to say who the user is, as described
in [Sessions and channels](https://vatio.ai/docs/authentication/sessions):

```dart
final chat = VatioChat(vatio, visitorToken: signedToken);
```

A different token subject is a different person with their own conversation,
so signing out of your app signs out of the chat: dispose it and create a new
one. An anonymous conversation is kept when the user signs in.

## Delivery and storage

Replies arrive over a WebSocket. When it drops, the SDK reconnects, polls in
the meantime, and fetches what it missed; messages are deduplicated by id.
`replyStyle` (`VatioReplyStyle.stream` by default, `paced`, `instant`) works as
on the [web](https://vatio.ai/docs/sdk/limits#reply-style).

The conversation and the visitor id are stored with `shared_preferences`, per
workspace and signed-in user. Chat credentials last 12 hours. Rate limits and
message length are the [same as on the web](https://vatio.ai/docs/sdk/limits). On the web, use the
[widget](https://vatio.ai/docs/channels/widget) or the [browser SDK](https://vatio.ai/docs/sdk/) instead: this package
runs on Android, iOS and macOS.
