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

# Flutter plugin

> Add Clarity's in-app support chat to a Flutter app with one Dart API over the Android and iOS SDKs.

The `onclarity` plugin is a thin Dart layer over the [Android](/sdks/android.md) and [iOS](/sdks/ios.md) SDKs. The chat opens as a native container over your Flutter UI, so nothing is added to your widget tree and your routes are untouched while it is open.

**Requirements:** Flutter 3.44 or later, Android API 21 or later, iOS 15 or later.

## Get your credentials

A Flutter app ships as two native apps, and Clarity matches a channel per platform, so you need two integration keys.

In the cabinet, go to **Settings > Mobile SDK**, click **Add Integration** and choose **Flutter**. Enter both App IDs:

| App ID | Where it comes from |
| - | - |
| Android | Your `applicationId` in `android/app/build.gradle` |
| iOS | Your bundle identifier (Xcode > Runner > General) |

You get one Organization Id and two integration keys, one per platform. The live chat host is `https://live-chat.leap.onclarity.com`. These values are identifiers, not secrets, so shipping them in the app is fine.

## Install

```bash Flutter CLI theme={null}
flutter pub add onclarity
```

```yaml pubspec.yaml theme={null}
dependencies:
  onclarity: ^0.3.0
```

Android needs nothing more: the native SDK comes from Maven Central.

### iOS: raise the deployment target

The iOS SDK needs iOS 15, and Flutter's app template targets iOS 13, so `pod install` fails until you raise it. Change it in both places:

```ruby theme={null}
# ios/Podfile (the template ships this line commented out)
platform :ios, '15.0'
```

and in Xcode, **Runner > General > Minimum Deployments > iOS 15.0**. Then run `pod install` in `ios/`.

### Android: Kotlin version

The plugin needs Kotlin 2.0 or later in your Android project. Flutter 3.44's template already has it. An older project fails with `compiled with an incompatible version of Kotlin`; raise the version in `android/settings.gradle`:

```groovy theme={null}
plugins {
    id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}
```

The Android build prints a warning that `onclarity` applies the Kotlin Gradle Plugin. It is harmless, and you have nothing to change.

## Initialize

Pass both keys once. The plugin picks the right one for the platform it runs on.

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

Future<void> main() async {
  await Clarity.init(const ClarityConfig(
    organizationId: 'YOUR_ORGANIZATION_ID',
    baseUrl: 'https://live-chat.leap.onclarity.com',
    androidIntegrationKey: 'YOUR_ANDROID_KEY',
    iosIntegrationKey: 'YOUR_IOS_KEY',
  ));
  runApp(const MyApp());
}
```

### Configuration options

| `ClarityConfig` field | Description |
| - | - |
| `organizationId`, `baseUrl` | Required. |
| `androidIntegrationKey`, `iosIntegrationKey` | The key of each platform's channel. |
| `androidAppId`, `iosAppId` | Override the App ID sent to Clarity. Default: the app's own identifier. |
| `androidPlatform` | `ClarityAndroidPlatform.huawei` for a Huawei channel. See [Huawei builds](#huawei-and-appgallery-builds). |
| `presentationMode` | `PresentationMode.bottomSheet` (default) or `PresentationMode.fullScreen`. |
| `sheetHeightFraction` | Share of the screen the bottom sheet covers. |
| `autoFocusInput` | Focus the message box when the customer enters a conversation. |
| `autoPreload` | Warm the chat in the background after `init`. On by default. |
| `language` | UI language of the chat, for example `'en'` or `'ar'`. |
| `showNativeCloseButton` | Draw an extra native close button. |
| `loadTimeout` | How long to wait for the chat to load, as a `Duration`. |
| `showDiagnosticsOnFailure` | Show a developer screen naming the reason when the chat fails to open. Debug builds only by default. |

## Identify the user

```dart theme={null}
await Clarity.identify(const ClarityUser(
  externalId: 'user-42',     // stable id in your system
  email: 'ada@example.com',
  firstName: 'Ada',
));
```

`ClarityUser` also takes `lastName`, `phone` and `avatar`. Changing `externalId` is an account switch and starts a separate conversation; changing other fields on the same id updates the profile. On logout:

```dart theme={null}
await Clarity.resetSession();
```

> [!WARNING]
>
> The Flutter plugin does not support signed identity (`jwt`) yet. Identity passed here is not verified, so do not turn on **Require a verified identity** for the channels your Flutter app uses. The `userHash` field is deprecated and not verified; do not rely on it.

## Open the chat

```dart theme={null}
await Clarity.show();                                   // bottom sheet
await Clarity.show(mode: PresentationMode.fullScreen);  // full screen
await Clarity.dismiss();                                // close from code
```

The chat appears only once it is ready. A failure arrives on the event stream instead of a blank sheet.

## Listen for events

Everything the native SDKs report arrives on one broadcast stream. `ClarityEvent` is a sealed class, so a `switch` over it is exhaustive:

```dart theme={null}
late final StreamSubscription<ClarityEvent> _sub;

@override
void initState() {
  super.initState();
  _sub = Clarity.events.listen((ClarityEvent event) {
    switch (event) {
      case ClarityUnreadCountChanged(:final int count):
        setState(() => _showBadge = count > 0);   // 1 = unseen replies, 0 = none
      case ClarityNewMessage(:final String textPreview):
        // an agent replied while the chat was closed
      case ClarityTicketStatusChanged(:final String status):
        // 'open' or 'closed'
      case ClarityErrorEvent(:final ClarityError error):
        if (!error.recoverable) {
          // fix the configuration; hide the Help entry or fall back to email
        }
      case ClarityWidgetShown():
      case ClarityWidgetDismissed():
      case ClarityWidgetFailed():
      case ClarityDebugEvent():
        break;
    }
  });
}

@override
void dispose() {
  _sub.cancel();
  super.dispose();
}
```

When the app returns to the foreground, call `await Clarity.refresh()` to pull anything missed.

> [!NOTE]
>
> The plugin owns the native listener and fans it out to `Clarity.events`. Do not register your own native listener on either platform.

## Attributes and product events

```dart theme={null}
// String, int, double and bool keep their type; List<String> selects option ids
// of a list attribute; null removes the attribute.
await Clarity.setUserAttributes(<String, Object?>{
  'plan': 'gold',
  'seat_count': 7,
  'vip': true,
});

// On the next ticket only:
await Clarity.setTicketAttributes(<String, Object?>{'order_id': 'A-1042'});

// Product events, for automations and AI agent context:
await Clarity.logEvent('checkout_failed', params: {'code': 'card_declined'});
```

An unsupported value type throws `ArgumentError` at your call site.

> [!NOTE]
>
> If your workspace marks contact attributes as required, set them right after `Clarity.init`, before the chat first loads. A session missing a required attribute is refused, and `show()` reports `ClarityErrorKind.pageLoadFailed` naming the missing keys. Alternatively pass `autoPreload: false` and call `Clarity.preload()` once the attributes are set.

## Push notifications

Clarity's push routing is FCM-based. With `firebase_messaging` in your app, forward the token and messages from Dart:

```dart theme={null}
FirebaseMessaging.instance.onTokenRefresh.listen(Clarity.setDeviceToken);
final String? token = await FirebaseMessaging.instance.getToken();
if (token != null) await Clarity.setDeviceToken(token);

FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
  if (await Clarity.handlePush(message.data)) return;  // Clarity handled it
  // your own handling
});
```

On iOS, a tap on a Clarity notification reopens the chat only if your `AppDelegate` forwards it, which Dart cannot do. Add this to `ios/Runner/AppDelegate.swift`:

```swift theme={null}
import ClaritySDK

func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    didReceive response: UNNotificationResponse,
    withCompletionHandler completionHandler: @escaping () -> Void
) {
    if Clarity.handleNotificationResponse(response) { completionHandler(); return }
    // your own handling
}
```

Android needs nothing more; the SDK handles its own notification taps. On Android 13 and later, request `POST_NOTIFICATIONS` yourself. The SDK never prompts.

> [!NOTE]
>
> Background push delivery is enabled per organization. Ask your Clarity contact about availability.

## Huawei and AppGallery builds

If your Android channel was created with **My mobile is Huawei**, tell the plugin in `ClarityConfig`:

```dart theme={null}
androidPlatform: ClarityAndroidPlatform.huawei,
```

Set it from your build flavour, not by probing the device. Chat works on devices without Google services; push does not, because push is FCM-only.

## Attachments, voice and dictation

* **Camera on iOS:** add `NSCameraUsageDescription` to `ios/Runner/Info.plist` to enable **Take Photo**. Photo library and file uploads work without it.
* **Voice calling and dictation:** declare microphone access, `RECORD_AUDIO` in the Android manifest and `NSMicrophoneUsageDescription` in the iOS Info.plist. Without it, the chat shows no microphone button.

## Customization

The chat's colours, logos, font and texts come from **Settings > Widget** in the cabinet, the same settings as the web widget. In code you control the presentation, `language` and `autoFocusInput`.

## API reference

| Call | Purpose |
| - | - |
| `Clarity.init(config)` | Once, at launch |
| `Clarity.updateConfig(config)` | Switch credentials at runtime |
| `Clarity.identify(user)` / `resetSession()` | Bind your user / logout |
| `Clarity.show(mode:, sheetHeightFraction:)` / `dismiss()` | Open and close |
| `Clarity.preload()` | Manual warm-up when `autoPreload` is off |
| `Clarity.refresh()` | Fetch the latest ticket state |
| `Clarity.setUserAttributes(map)` / `setTicketAttributes(map)` / `logEvent(name, params:)` | Context |
| `Clarity.events` | Stream of every listener callback |
| `Clarity.unreadCount()` / `isWidgetVisible()` / `currentTicketStatus()` | Current state |
| `Clarity.setDeviceToken(token)` | Forward the push token |
| `Clarity.isClarityPush(data)` / `handlePush(data)` | Route pushes |
| `Clarity.previewWidgetUrl()` / `getConfig()` | Debugging |

## Troubleshooting

> **Nothing opens**
>
> Listen to `Clarity.events` for `ClarityErrorEvent`. `handshakeRejected` means a wrong organization id, integration key or App ID (each channel's App ID must equal that platform's identifier), or a Huawei platform mismatch. `network` means the device cannot reach `baseUrl`.

> **pod install fails with a platform error**
>
> Raise the iOS deployment target to 15.0 in `ios/Podfile` and in Xcode, as in [Install](#ios-raise-the-deployment-target).

> **Kotlin metadata version error on Android**
>
> Raise your project's Kotlin Gradle plugin to 2.0 or later, as in [Install](#android-kotlin-version).

> **Camera option missing on iOS**
>
> Add `NSCameraUsageDescription` to `ios/Runner/Info.plist`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.