Skip to main content

Android Integration Guide

Get your API key from https://app.revrag.ai.

Table of Contents

  1. Requirements
  2. Step 1 — Add the dependency
  3. Step 2 — Manifest permissions
  4. Step 3 — Initialize the SDK
  5. Step 4 — Identify the user
  6. Step 5 — Add the floating button
  7. Routing scenarios
  8. Button visibility control
  9. Voice calls & widget control
  10. Call behavior
  11. Events
  12. Analytics helpers
  13. Observing all SDK events
  14. Cleanup on logout
  15. Configuration reference
  16. Troubleshooting
  17. Pre-ship checklist

Requirements

Kotlin version: The SDK is compiled with Kotlin 2.0.21, which emits Kotlin metadata 2.x. Consumers on Kotlin 1.9 or below will see Class 'X' was compiled with an incompatible version of Kotlin — bump your project to Kotlin 2.0+. If your own app pulls in Ktor 3.4+, its transitive stdlib forces Kotlin 2.3+.HTTP client isolation: The SDK bundles a private, relocated copy of Ktor under ai.revrag.shaded.ktor.*. You can use any version of Ktor (or none at all) in your own app without conflict — past, present, or future.

Step 1 — Add the dependency

In your module-level build.gradle.kts:
Sync Gradle. No extra repository setup needed — mavenCentral() is already in every Android project. The SDK transitively pulls in:
  • LiveKit Android SDK (WebRTC)
  • Lottie for Android
  • Coil (image loading)
  • Kotlin Coroutines
Public package surface: import everything from ai.revrag.embed.android. The one exception is EmbedButtonInset (button positioning), which lives in ai.revrag.embed.android.ui. Other internal packages may change without notice.

Step 2 — Manifest permissions

The SDK’s AndroidManifest.xml declares these permissions — they are auto-merged into your app via manifest merger. You do not need to add them manually.
RECORD_AUDIO and BLUETOOTH_CONNECT (API 31+) are runtime permissions. The SDK requests them automatically when the user taps the call button — you do not trigger them yourself.

Step 3 — Initialize the SDK

Create an Application class and call initialize() in onCreate(). This must run once, as early as possible.
Register the Application class in AndroidManifest.xml:
What initialize() does:
  1. Calls GET /embedded-agent/initialize with your API key
  2. Downloads and caches your widget configuration (colors, agent name, avatar)
  3. Pre-warms the Lottie animation cache to avoid a first-render flash
  4. Pre-fetches static icon assets via Coil
  5. Sets EmbedSDK.isInitialized = true — the button appears automatically once this is true

Step 4 — Identify the user

Call this right after your login flow completes:
Send USER_DATA before the user taps the call button. Without app_user_id, the agent cannot identify the user and conversation context will not be attributed.

Step 5 — Add the floating button

Wrap your root composable with EmbedProvider.attach() in MainActivity. This overlays the draggable button on top of all your existing Compose content automatically.

Alternative — EmbedProviderComposable

If you already call setContent {} yourself or use a multi-Activity architecture:
EmbedProviderComposable must be placed at the root of the Compose tree so BoxWithConstraints receives full-screen bounds. If it’s nested inside a sized container the button will be stuck at the top-left corner.

EmbedProvider.attach() props


Routing scenarios

Screen tracking lets the agent know which screen the user is on. Choose the scenario that matches your navigation setup.

Scenario A — Single NavHost (most common)

Pass the NavController to EmbedProvider. The SDK tracks screen changes automatically — no additional code required.
Screen names reported to Revrag are the route strings: "home", "search", "product/{id}", etc.

Scenario B — Bottom navigation with multiple NavHosts

Each tab has its own NavController. Pass the active one to EmbedProvider:
The SDK reports whichever screen is active inside the currently visible tab’s NavHost.

Scenario C — No NavController (manual screen tracking)

If you manage navigation yourself (custom back stack, Fragments, or plain Compose without NavController), fire SCREEN_STATE events manually:
In a Fragment:
In a Compose screen without NavController:

Scenario D — Nested navigation (checkout flow inside tabs)

The SDK automatically picks up "cart", "payment", "confirmation", etc. as the screen names from the nested graph.

Button visibility control

By default the button shows on every screen. Use EmbedButtonVisibilityConfig to control this.

Show only on specific screens

Keep button visible across a flow (e.g. checkout)

Use a group with .CONTINUOUS continuity so the button doesn’t flash between screens:

Visibility rules (evaluated in priority order)


Voice calls & widget control

The floating button (FAB) and its call card handle everything on their own. These functions let you drive the same widget programmatically — start or end a call, check call state, and collapse or observe the card — without waiting for a user tap.

Call control

startCall returns false if no app_user_id is stored. Always send a USER_DATA event before starting a call.

Widget control

Expanding is not exposed — the widget opens on a user tap or via a dashboard-configured auto_trigger. Your app can collapse it and observe its state.

Call behavior

Behavior to know about while a call is active:

Events

EventKeys reference

Sending events

Listen for agent call events

Agent lifecycle events reference

Listen to data events locally

Use on() / off() to observe any EventKeys event locally — for example to mirror USER_DATA into your own state. Pass the same callback reference to off() when removing it.

Analytics helpers

These helpers fire ANALYTICS_DATA events with standardized payloads.

Events auto-fired by the SDK

Matching event names with EmbedAnalyticsEvents

The SDK fires its built-in events as ANALYTICS_DATA carrying an event_name. Match against these constants instead of hardcoding strings:

Utility methods


Observing all SDK events

The SDK emits its own analytics / lifecycle events (widget taps, call start, mic permission, rage-clicks, tool triggers, errors…) — all as ANALYTICS_DATA events carrying an event_name. Drop this helper into your app to log them all in one place or forward them to your analytics.

Usage

Full helper


Cleanup on logout

Call this when the user logs out or switches accounts to prevent stale data from leaking into the next session.

Configuration reference

EmbedButtonVisibilityConfig

EmbedButtonGroupConfig

EmbedButtonDelayPolicy

EmbedButton direct props


Troubleshooting

The button never appears

Cause A — initialize() not called or failed Check Logcat for [EmbedSDK] tags:
Fix: verify your API key has no leading/trailing spaces and the device has internet.
Cause B — SDK not ready yet initialize() is async. The button is hidden until isInitialized = true and appears automatically — no action required. To observe readiness:

Cause C — Screen excluded by visibility config Check excludedScreens and allowedScreens. Screen names are case-sensitive and must match exactly what’s in your NavGraph routes or what you pass to SCREEN_STATE.

Upgrading from 1.0.7 → 1.0.8

Drop-in replacement. No code changes required — bump the version string only. 1.0.8 adds the host-callable call-control API (startCall / endCall / isCallActive), widget control (collapseWidget / isWidgetExpanded), and the EmbedAnalyticsEvents constants; all existing 1.0.7 code keeps working unchanged.

Upgrading from 1.0.6 → 1.0.7

Drop-in replacement. No code changes required — bump the version string only. If you were blocked on 1.0.6 by errors like:
…or any other Ktor classpath conflict, 1.0.7 fixes it. The SDK now ships its own Ktor under ai.revrag.shaded.ktor.*, so your app’s Ktor version no longer matters.

Class 'X' was compiled with an incompatible version of Kotlin

Cause: Your project is on Kotlin 1.9 or earlier. The SDK is compiled with Kotlin 2.0.21 and the K1 compiler cannot read Kotlin metadata 2.x. Fix: bump your project’s Kotlin version to 2.0+ in your root build.gradle.kts:
If your app also pulls in Ktor 3.4+, you’ll need Kotlin 2.3+ (Ktor 3.4’s transitive stdlib forces this).

Could not resolve ai.revrag:embed-android:1.0.8

Cause: Dependency not cached, or stale Gradle cache.
Verify the artifact is live: https://central.sonatype.com/artifact/ai.revrag/embed-android Confirm mavenCentral() is in your settings.gradle.kts:

Button stuck at top-left corner

Cause: EmbedProviderComposable is not at the root of the Compose tree.

Audio plays through speaker instead of Bluetooth headset

Cause: BLUETOOTH_CONNECT runtime permission denied (Android 12+). Direct the user to app settings:

Analytics events missing from dashboard

Check in this order:
  1. USER_DATA was sent with a valid app_user_id before other events
  2. initialize() completed successfully (isInitialized == true)
  3. Device has internet connectivity
  4. The SDK rate-limits to 5 req/s — bursts are queued, not dropped

User identity leaks between accounts

Always call clearStorageCache() on logout before the next user logs in:

Pre-ship checklist

Basic setup

  • ai.revrag:embed-android:1.0.8 added and Gradle synced
  • Application class created and registered in AndroidManifest.xml
  • EmbedSDK.initialize() called in Application.onCreate()
  • USER_DATA event sent with app_user_id immediately after login
  • EmbedProvider.attach() wraps the root composable in MainActivity
  • clearStorageCache() called on logout

Screen tracking

  • NavController passed to EmbedProvider (Scenario A/B), or SCREEN_STATE fired manually (Scenario C)
  • Nested graphs tracked correctly (Scenario D if applicable)

Visibility

  • visibilityConfig configured if the button should not show on all screens
  • Screen names in config match routes exactly (case-sensitive)

Production readiness

  • Error handling in EmbedSDK.initialize() onResult callback
  • Agent event listeners registered and de-registered in onDestroy
  • If driving calls from your own UI, USER_DATA is sent before startCall()
  • Tested on a physical device — microphone permission dialog does not appear in emulator

Support