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

# Android SDK

> Add Clarity's in-app support chat to an Android app: install, initialize, identify users, push notifications.

The Android SDK shows Clarity's support chat as a native bottom sheet or full-screen view. It pre-warms the chat in the background so it opens instantly, reports new replies for your own unread badge, and never crashes your app: every failure is reported to you instead.

**Requirements:** Android 5.0 (API 21) or later, Kotlin 1.9 or later, and Android System WebView, which every Google-certified device has.

## Get your credentials

In the cabinet, go to **Settings > Mobile SDK**, click **Add Integration**, choose **Android** and enter your app's `applicationId` as the **App ID**. The integration dialog shows:

| Value | Where it goes |
| - | - |
| Organization Id | `ClarityConfig.organizationId` |
| Integration key | `ClarityConfig.integrationKey` |
| Live chat host | `ClarityConfig.baseUrl`: `https://live-chat.leap.onclarity.com` |

The channel's **App ID** must equal your app's `applicationId`; the SDK sends it when the chat session starts and a mismatch is refused. These values are identifiers, not secrets, so shipping them in the APK is fine.

> [!NOTE]
>
> For an AppGallery build, create the channel with **My mobile is Huawei** checked and pass `platform = ClarityPlatform.HUAWEI` in `ClarityConfig`. Set it from your build flavour, not by probing the device. Chat works on devices without Google services; push notifications do not, because push is FCM-only.

## Install

The SDK is on Maven Central, so no extra repository is needed.

```kotlin Kotlin DSL theme={null}
dependencies {
    implementation("com.onclarity:clarity-android-sdk:1.6.1")
}
```

```groovy Groovy theme={null}
dependencies {
    implementation 'com.onclarity:clarity-android-sdk:1.6.1'
}
```

## Initialize

Call `Clarity.init` once, in `Application.onCreate`:

```kotlin theme={null}
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        Clarity.init(
            applicationContext = this,
            config = ClarityConfig(
                organizationId = "YOUR_ORGANIZATION_ID",
                integrationKey = "YOUR_INTEGRATION_KEY",
                baseUrl = "https://live-chat.leap.onclarity.com",
            ),
        )
    }
}
```

### Configuration options

| `ClarityConfig` field | Default | Description |
| - | - | - |
| `presentationMode` | `PresentationMode.BOTTOM_SHEET` | Default presentation for `show()`. `FULL_SCREEN` is the alternative. |
| `sheetHeightFraction` | `0.85` | Share of the screen the bottom sheet covers, clamped to 0.3–1.0. |
| `autoFocusInput` | `false` | Focus the message box and raise the keyboard when the customer enters a conversation. |
| `autoPreload` | `true` | Warm the chat in the background after `init` so the first open is instant. |
| `language` | App locale | UI language of the chat, for example `"en"` or `"ar"`. |
| `appId` | Your `applicationId` | Must match the channel's App ID. |
| `platform` | `ClarityPlatform.ANDROID` | `ClarityPlatform.HUAWEI` for a Huawei channel. |
| `showNativeCloseButton` | `false` | Draw an extra native close button. The chat has its own. |
| `loadTimeoutMillis` | `15000` | How long to wait for the chat to load before reporting a failure. |
| `showDiagnosticsOnFailure` | Debug builds only | Show a developer screen naming the reason when the chat fails to open. |

## Identify the user

After your user logs in, and before the first open:

```kotlin theme={null}
Clarity.identify(
    ClarityUser(
        externalId = user.id,        // stable id in your system
        firstName = user.firstName,
        lastName = user.lastName,
        email = user.email,
    )
)
```

`ClarityUser` also takes `phone`, `avatar` and `jwt`.

* Calling `identify` with a **different** `externalId` is a logout plus login: the previous user's session is wiped, so conversations never leak between users.
* Calling it again with the **same** `externalId` and new profile fields updates the profile and keeps the conversation.
* On logout, call `Clarity.resetSession()`.

### Verify the user (signed identity)

An `externalId` alone is a claim the app makes about itself. To have Clarity trust it, your backend signs a short-lived JWT for the user with the channel's identity secret, and the app passes it as `jwt`:

```kotlin theme={null}
val token = myBackend.fetchClarityToken()   // your own authenticated endpoint

Clarity.identify(
    ClarityUser(
        externalId = user.id,
        email = user.email,
        jwt = token,
    )
)
```

The secret is in the Android integration's dialog under **Identity verification**. The token format, the claims and the **Require a verified identity** switch are the same as for the web widget: see [Verify the visitor](/sdks/web-widget.md#verify-the-visitor-signed-identity).

* When the token expires, fetch a new one and call `identify` again with the same `externalId`. The conversation is kept.
* A refused token makes `show()` report `PAGE_LOAD_FAILED` with code `401`, and the message names the reason.

> [!WARNING]
>
> Never put the identity secret in the app. Anyone who extracts it from the APK can impersonate any of your users. `ClarityUser.userHash` is deprecated and was never verified; use `jwt`.

## Open the chat

```kotlin theme={null}
Clarity.show()                                // bottom sheet, config height
Clarity.show(PresentationMode.FULL_SCREEN)    // full screen
Clarity.show(sheetHeightFraction = 0.9f)      // one-off taller sheet
Clarity.dismiss()                             // close it from code
```

The chat appears only once it has loaded and painted. If it cannot load, the failure arrives through your listener instead of a blank sheet. Back, a tap outside the sheet and the chat's own close button all dismiss it.

## Badge your Help button

Every `ClarityListener` method is optional:

```kotlin theme={null}
Clarity.setListener(object : ClarityListener {
    override fun onNewMessage(textPreview: String) {
        // an agent replied while the chat was closed
    }
    override fun onUnreadCountChanged(count: Int) {
        helpBadge.isVisible = count > 0   // 1 = unseen replies, 0 = none
    }
    override fun onTicketStatusChanged(status: String) {
        // "open" or "closed"
    }
    override fun onError(error: ClarityError) {
        if (!error.recoverable) { /* hide the Help entry or fall back to email */ }
    }
})
```

You can also read `Clarity.unreadCount`, `Clarity.isWidgetVisible` and `Clarity.currentTicketStatus` directly. `unreadCount` is a flag: `1` while unseen replies exist, else `0`.

Delivery while the chat is closed is best effort. When freshness matters, for example when the app returns to the foreground, call `Clarity.refresh()`. Missed replies and status changes arrive through the same listener.

## Attributes and product events

```kotlin theme={null}
// On the contact, long-lived. String, Number and Boolean keep their type;
// List<String> selects option ids of a list attribute; null removes the attribute.
Clarity.setUserAttributes(mapOf("plan" to "gold", "seat_count" to 7, "vip" to true))

// On the next ticket the customer opens, and only that one:
Clarity.setTicketAttributes(mapOf("order_id" to "A-1042"))

// Product events, for automations and AI agent context:
Clarity.logEvent("checkout_failed", mapOf("code" to "card_declined"))
```

These calls are safe from any thread at any time; values queue until the chat session is live. Product events are processed once product events are enabled for your organization; ask your Clarity contact.

> [!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 `PAGE_LOAD_FAILED` naming the missing keys. With `autoPreload = true` the first load starts about 1.5 seconds after `init`; alternatively set `autoPreload = false` and call `Clarity.preload()` once the attributes are set.

## Push notifications

While your app is running, replies that arrive with the chat closed already show a system notification; a tap reopens the chat. On Android 13 and later, request `POST_NOTIFICATIONS` yourself. The SDK never prompts.

For replies while your app is killed or long in the background, forward your Firebase token and let Clarity handle its own pushes:

```kotlin theme={null}
class MyFirebaseService : FirebaseMessagingService() {
    override fun onNewToken(token: String) = Clarity.setDeviceToken(token)

    override fun onMessageReceived(message: RemoteMessage) {
        if (Clarity.handlePush(this, message.data)) return   // Clarity shows it and handles the tap
        // your own push handling
    }
}
```

Also call `Clarity.setDeviceToken(...)` once at app start with the current token. `Clarity.isClarityPush(data)` tells you whether a message is Clarity's without handling it.

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

## Attachments, voice and dictation

Customers can attach images and PDFs through the system file picker. No permissions or manifest entries are needed.

If your organization uses voice calling or dictation in the chat, declare the microphone permission in your manifest:

```xml theme={null}
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```

With it declared, the chat offers a microphone button and asks for the permission on first use. Without it, no microphone button is shown.

## Customization

The chat's colours, logos, font and texts come from **Settings > Widget** in the cabinet, the same settings as the web widget. Per-app switches such as voice calling, dictation and the Inbox tab are in the Android integration's dialog. In code you control the presentation (`presentationMode`, `sheetHeightFraction`), `language` and `autoFocusInput`.

## API reference

| Call | Purpose |
| - | - |
| `Clarity.init(context, config)` | Once, in `Application.onCreate` |
| `Clarity.updateConfig(config)` | Switch credentials at runtime |
| `Clarity.identify(ClarityUser)` | Bind your user, including the signed `jwt` |
| `Clarity.resetSession()` | Logout |
| `Clarity.show(mode?, sheetHeightFraction?)` / `Clarity.dismiss()` | Open and close |
| `Clarity.preload()` | Manual warm-up when `autoPreload = false` |
| `Clarity.refresh()` | Fetch the latest ticket state |
| `Clarity.setUserAttributes(map)` / `setTicketAttributes(map)` / `logEvent(name, params)` | Context |
| `Clarity.setDeviceToken(token)` | Forward the FCM token |
| `Clarity.isClarityPush(data)` / `handlePush(context, data)` | Route FCM messages |
| `Clarity.setListener(listener)` / `unreadCount` | Callbacks and unread flag |
| `Clarity.isWidgetVisible` / `currentTicketStatus` | Current state |
| `Clarity.previewWidgetUrl(...)` | Debugging: the URL the SDK would load |

## Troubleshooting

Every failure arrives in `onError` as a `ClarityError` with a `kind`:

| `ClarityErrorKind` | Meaning | Recoverable |
| - | - | - |
| `NETWORK` | Offline, host unreachable or a server error | Yes; the next `show()` retries |
| `HANDSHAKE_REJECTED` | Wrong organization id, integration key, App ID or platform | **No**; fix the configuration |
| `PAGE_LOAD_FAILED` | The chat loaded but never became ready, a required attribute is missing, or the identity token was refused | Yes; the next `show()` reloads |
| `RENDERER_CRASHED` | The WebView renderer died | Yes; recovers on its own |
| `INTERNAL` | Unexpected failure, including WebView missing or updating | Yes |

> **Nothing appears on show()**
>
> Check `onError`. `HANDSHAKE_REJECTED` means the organization id, integration key or App ID is wrong (the App ID must equal your `applicationId`), or the channel is Huawei and `platform` is not `ClarityPlatform.HUAWEI`, or the reverse. `NETWORK` means the device cannot reach `baseUrl`: check that Chrome on the same device opens it. In debug builds, a diagnostic screen names the reason.

> **Replies while the chat is closed do not notify**
>
> On Android 13 and later, request `POST_NOTIFICATIONS`. The SDK never prompts for it.

> **Voice calls fail with "Could not start voice chat"**
>
> Update to 1.6.1, which declares `MODIFY_AUDIO_SETTINGS`. On an older version, add `<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />` to your manifest.

> **The chat does not open after adding jwt**
>
> `show()` reports `PAGE_LOAD_FAILED` with code `401`. The message says whether the token was invalid, expired, missing or malformed. Check that your backend signs with the channel's current identity secret and sets a valid `exp`.


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