# 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="https://3978076094-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2VnkvXtkrR2qhkVBmB1l%2Fuploads%2Fgl5zjWcycOSXc75z8lVa%2Fimage.png?alt=media&#x26;token=e58b07fd-ef5e-4a50-ae68-0f0b9e5cc78f" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://3978076094-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F2VnkvXtkrR2qhkVBmB1l%2Fuploads%2FKzZWOhRKmg4rUxn3NqtZ%2Fimage.png?alt=media&#x26;token=07f8f509-2b37-4e55-9b07-14e900b35484" 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.
