# Android

{% hint style="info" %}
This is an enterprise only package available under NDA.
{% endhint %}

A native Android SDK that provides a customizable conversational interface that you can embed in your Android applications. Touchpoint allows users to interact with your NLX applications through natural language and provides a seamless conversational experience.

### Overview

This SDK provides:

* Voice conversations via NLX (real‑time, full‑duplex audio)
* *Floating widget*: a compact bubble with mic/speaker/close controls that snaps to screen edges
* *Bidirectional form interaction*: the assistant sends field updates over a WebSocket (“voice‑plus” channel); your app applies them to native views
* *Simple integration*: one configuration object and a single custom view

### Requirements

* *Android*: API 29+ (Android 10+).
* *Kotlin*: 1.8+
* *Gradle*: Android Gradle Plugin 8+
* *Permissions*: `RECORD_AUDIO`, `INTERNET`
* *Network*: TLS/WSS access to NLX endpoints

### Installation

You can include the SDK as a module inside your app, or publish it to a repository and consume it as a dependency.

**Option A: Local module (recommended while iterating)**

1. Copy the SDK modules into your project (e.g., `core/`, `voiceengine/`, `touchpointui/`).
2. In your `settings.gradle`:

   ```kotlin
   include(":nlx-core", ":nlx-voiceengine", ":nlx-touchpointui")
   ```
3. In your app’s `build.gradle`:

   ```kotlin
   dependencies {
       implementation(project(":nlx-core"))
       implementation(project(":nlx-voiceengine"))
       implementation(project(":nlx-touchpointui"))
   }
   ```

**Option B: Composite build (Git submodule)**

1. Add the SDK repo as a git submodule (or plain checkout) at, say, `third_party/nlx-sdk-android`.
2. In your root `settings.gradle`:

   ```kotlin
   includeBuild("third_party/nlx-sdk-android")
   ```
3. Now you can depend on the included modules without publishing:

   ```kotlin
   dependencies {
       implementation("com.yourorg.nlx:nlx-core")          // resolved from the composite build
       implementation("com.yourorg.nlx:nlx-voiceengine")
       implementation("com.yourorg.nlx:nlx-touchpointui")

   ```

{% hint style="info" %}
Tag the SDK repo (e.g., `v0.1.0`) and pin your submodule to that tag to lock versions across teams.
{% endhint %}

**Option C: Maven repository (GitHub Packages – private)**

1. Publish artifacts to GitHub Packages and consume them by version tags.
2. Publishing (in each module’s `build.gradle.kts`):

```kotlin
plugins {
    id("maven-publish")
}

group = "com.yourorg.nlx"
version = "0.1.0" // bump this and create a git tag v0.1.0

publishing {
    publications {
        create<MavenPublication>("release") {
            from(components["release"]) // for Android libraries; use "java" for pure JVM modules
            artifactId = "nlx-touchpointui" // change per module
        }
    }
    repositories {
        maven {
            name = "GitHubPackages"
            url = uri("https://maven.pkg.github.com/YOUR_GH_ORG/YOUR_REPO")
            credentials {
                username = System.getenv("GITHUB_ACTOR") ?: "YOUR_GH_USER"
                password = System.getenv("GITHUB_TOKEN") ?: "YOUR_PAT_WITH_read:packages_write:packages"
            }
        }
    }
}
```

3. Consume (in your app):

```kotlin
// settings.gradle or repositories { ... } in build.gradle
repositories {
    mavenCentral()
    maven {
        url = uri("https://maven.pkg.github.com/YOUR_GH_ORG/YOUR_REPO")
        credentials {
            username = providers.environmentVariable("GITHUB_ACTOR").orNull ?: "YOUR_GH_USER"
            password = providers.environmentVariable("GITHUB_TOKEN").orNull ?: "YOUR_PAT_WITH_read:packages"
        }
    }
}

// build.gradle
dependencies {
    implementation("com.yourorg.nlx:nlx-core:0.1.0")
    implementation("com.yourorg.nlx:nlx-voiceengine:0.1.0")
    implementation("com.yourorg.nlx:nlx-touchpointui:0.1.0")
}
```

4\. Create a tag per release and publish:

```bash
git tag v0.1.0
git push origin v0.1.0
./gradlew publish
```

Consumers then use `0.1.0` in their Gradle dependencies.

**Option D: JitPack (public)**

If the repo can be public, you may use JitPack:

1. Push a version tag (e.g., `v0.1.0`) to your GitHub repo.
2. In your app:

   ```kotlin
   repositories {
       maven { url = uri("https://jitpack.io") }
   }
   dependencies {
       // For multi-module builds, artifactId is each module name you publish
       implementation("com.github.YOUR_GH_USER:nlx-android-sdk:0.1.0") // single-module example
       // or
       implementation("com.github.YOUR_GH_USER:YOUR_REPO_MODULE:0.1.0") // if JitPack exposes modules separately
   }
   ```

{% hint style="info" %}
Check JitPack’s build log for the exact artifact coordinates if you publish multiple modules.
{% endhint %}

### Authentication

NLX uses API Keys to authenticate requests.  Touchpoint requires an Application URL and an API Key to be included in the config object.

<pre class="language-objc"><code class="lang-objc"><strong>// Configure the SDK
</strong>val nlxConfig = NlxConfig(
    applicationUrl = "YOUR_APPLICATION_URL",
    headers        = mapOf("nlx-api-key" to "YOUR_API_KEY"),...
</code></pre>

&#x20;The Application URL and API Key are located in the *API Delivery channel* setup.

<figure><img src="/files/HDYzUTwAdZ4sDAIPE37K" alt=""><figcaption></figcaption></figure>

Click *Setup instructions* to view Application URL and API Key.

<figure><img src="/files/tRa4lhS57vtOe5xTIy7e" alt=""><figcaption></figcaption></figure>

### Quick Start

{% stepper %}
{% step %}

### Manifest & runtime permission

```xml
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
```

Request `RECORD_AUDIO` at runtime on Android 10+.

{% endstep %}

{% step %}

### Configure the SDK

```kotlin
val nlxConfig = NlxConfig(
    applicationUrl = "https://bots.studio.nlx.ai/c/DEPLOYMENT/CHANNEL",
    headers        = mapOf("nlx-api-key" to "YOUR_API_KEY"),
    conversationId = UUID.randomUUID().toString(),
    userId         = UUID.randomUUID().toString(),
    languageCode   = "en-US"
)

val tpConfig = TouchPointConfiguration(
    nlxConfig = nlxConfig,
    input = Input.VoiceMini,
    bidirectional = TouchPointConfiguration.Bidirectional.Automatic(
        TouchPointConfiguration.AutomaticBidirectionalConfig(
            navigation = { command, payload -> /* handle navigation */ },
            input      = { fieldId, value, meta -> formBinder.applyFieldUpdate(fieldId, value) },
            custom     = { name, payload -> /* custom actions */ }
        )
    )
)
```

{% endstep %}

{% step %}

### Add the floating widget

```xml
<com.nlx.touchpointui.TouchPointWidgetView
    android:id="@+id/touchpointWidget"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
```

{% endstep %}

{% step %}

### Provide screen context & show the widget

```kotlin
class MainActivity : AppCompatActivity() {
  private lateinit var widget: TouchPointWidgetView

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    widget = findViewById(R.id.touchpointWidget)

    val context = buildFlightDemoContext(
        tripType = "", departure = "", destination = "",
        departureDate = "", returnDate = "",
        numPassengers = "", cabinClass = "",
        directOnly = false, flexibleDates = false, nearbyAirports = false
    )

    widget.setCurrentContext { context }
    widget.configure(tpConfig)

    findViewById<FloatingActionButton>(R.id.fabNlx).setOnClickListener {
        it.visibility = View.GONE
        widget.show() // connects to NLX and opens the Voice+ WebSocket
    }
    widget.setOnCloseActionListener {
        findViewById<FloatingActionButton>(R.id.fabNlx).visibility = View.VISIBLE
    }
  }
}
```

{% endstep %}
{% endstepper %}

### Bidirectional form updates

When the assistant wants to fill a field, you’ll receive JSON over the voice‑plus WebSocket, e.g.:

```json
{
  "classification": "input",
  "fields": [
    { "id": "input-4", "value": "2025-09-03" },
    { "id": "input-5", "value": "2025-09-08" }
  ],
  "conversationId": "…"
}
```

These updates are surfaced through the `input` callback you set in `TouchPointConfiguration`:

```kotlin
val handleForm: (String, Any?, Map<String, Any?>) -> Unit = { fieldId, value, meta ->
    formBinder.applyFieldUpdate(fieldId, value)
}
```

*Mapping IDs to views***:** keep a map from NLX field IDs to Android view tags/IDs and update the view:

```kotlin
private val idAlias = mapOf(
    "input-2" to "departure",
    "input-3" to "destination",
    "input-4" to "departureDate",
    "input-5" to "returnDate",
    "select-6" to "numPassengers",
    "select-7" to "cabinClassType",
    "input-8" to "directOnly",
    "input-9" to "flexibleDates",
    "input-10" to "nearbyAirports",
    "input-0" to "tripType",   // radio group
    "input-1" to "tripType"
)
```

For RadioGroups, maintain an input‑ID → value table and select the matching option:

```kotlin
private val radioValueById = mapOf(
    "input-0" to "round-trip",
    "input-1" to "one-way"
)
```

### Screen context

Provide a structured view of the current screen so the assistant knows your fields:

```json
{
  "nlx:vpContext": {
    "fields": [
      {"id":"input-2","name":"departure","type":"text","placeholder":"Enter departure city","value":""},
      {"id":"input-3","name":"destination","type":"text","placeholder":"Enter destination city","value":""},
      {"id":"input-4","name":"departureDate","type":"date","value":""},
      {"id":"input-5","name":"returnDate","type":"date","value":""},
      {"id":"select-6","name":"numPassengers","type":"select-one","value":"1","options":[...]}
    ],
    "destinations": []
  }
}
```

Use the provided `buildFlightDemoContext(...)` as a template and resend context when your screen changes.

### How it Works

* `NLX.conversation().getVoiceCredentials()` fetches a **token** for your `conversationId`.
* `VoiceEngine` connects to NLX for **real‑time audio**.
* In parallel, the SDK opens a **WebSocket** (`voice‑plus`) using your deployment key, bot channel (botId‑language), `conversationId`, and API key.
* The assistant publishes **form updates**, **navigation**, and **custom** actions over that socket; the SDK parses and routes them to your callbacks.

{% hint style="info" %}
The `conversationId` must match across REST, NLX, and the WebSocket URL (the SDK handles this).
{% endhint %}

### Troubleshooting

* *403 Forbidden on WebSocket*: Add `Origin: https://demos.nlx.ai` (or your approved origin) and ensure a valid `nlx-api-key`. Double‑check `deploymentKey`, `channelKey` (`BOTID-LANG`), `languageCode`, and `conversationId` in the URL.
* *404 Not Found on WebSocket*: The URL is malformed. Pattern: `wss://us-east-1-ws.bots.studio.nlx.ai/?deploymentKey=…&channelKey=BOTID-LANG&languageCode=…&conversationId=…&type=voice-plus&apiKey=…`
* *No form updates*:
  1. Ensure your `input` callback is set.
  2. Provide a correct context (IDs, types).
  3. Verify `conversationId` consistency.
  4. Confirm the agent flow actually emits updates (try the web demo with the same bot).
* Bot gets confused after playback: Don’t auto‑call `StructuredRequest(poll = true)` on every playback end.

### API surface (selected)

#### `NlxConfig`

```kotlin
data class NlxConfig(
  val applicationUrl: String,
  val headers: Map<String, String>,
  val languageCode: String = "en-US",
  val userId: String? = null,
  val conversationId: String? = null
)
```

#### `TouchPointConfiguration`

```kotlin
data class TouchPointConfiguration(
  val nlxConfig: NlxConfig,
  val input: Input = Input.VoiceMini,
  val bidirectional: Bidirectional = Bidirectional.Disabled
) {
  sealed class Bidirectional {
    data object Disabled : Bidirectional()
    data class Automatic(val config: AutomaticBidirectionalConfig) : Bidirectional()
    data class Manual(val controller: BidirectionalController) : Bidirectional()
  }
  data class AutomaticBidirectionalConfig(
    val navigation: ((String, Map<String, Any?>) -> Unit)? = null,
    val input: ((String, Any?, Map<String, Any?>) -> Unit)? = null,
    val custom: ((String, Map<String, Any?>) -> Unit)? = null
  )
}
```

#### `TouchPointWidgetView`

* `configure(config)` – pass your `TouchPointConfiguration`.
* `setCurrentContext { … }` – provide a context builder lambda.
* `show()` – connects to NLX and opens Voice+ WebSocket.
* `setOnCloseActionListener { … }` – receive close events.
* Visual states: Idle, Connecting, Active; mic/speaker color hints reflect who is speaking.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.nlx.ai/platform/developers/conversation-sdk/setup/android.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
