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

# Integration Checklist

> Step-by-step walkthrough from zero to a working GBGBridge integration.

A step-by-step walkthrough from zero to a working GBGBridge integration. Follow this page in order; each step links to the detailed guide if you need more context.

## Prerequisites

* [ ] An Android project with minSdk 24 (Android 7.0) or higher and compileSdk 34
* [ ] JDK 17 and Kotlin 2.x
* [ ] Access to Maven Central (no credentials or extra repositories required)
* [ ] A journey URL from the GBG Go Core SDK (see [Journey URL](/docs/go-v2/developer-integration/sdks/android/journey-url))

## Step 1: Add the SDK

Add GBGBridge from Maven Central in your module's `build.gradle.kts`:

```kotlin theme={null}
dependencies {
  implementation("com.gbg:gbgbridge-sdk:0.1.0-alpha01")
}
```

Make sure `mavenCentral()` is in your repository list — no other repository setup is needed.

<Note>See [Getting Started — Installation](/docs/go-v2/developer-integration/sdks/android/getting-started#installation) for full details.</Note>

## Step 2: Obtain a journey URL

Your app needs a journey URL to load inside the bridge WebView. This URL is generated server-side using the **GBG Go Core SDK** — your backend calls the Core SDK, receives a session URL, and passes it to the Android app.

<Note>See [Journey URL](/docs/go-v2/developer-integration/sdks/android/journey-url) for the full pattern, including URL shape, authentication, and configuration options.</Note>

## Step 3: Add manifest entries

Declare the permissions your integration uses in `AndroidManifest.xml`:

```xml theme={null}
<!-- Required: the WebView loads the journey over the network -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Required for document and selfie capture -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
    android:name="android.hardware.camera"
    android:required="false" />
```

Unlike iOS usage descriptions, the `CAMERA` permission must also be requested at runtime — handle that inside your capture surface (see [Capture Screens](/docs/go-v2/developer-integration/sdks/android/capture-screens)).

Android blocks cleartext HTTP by default (API 28+) — the equivalent of App Transport Security on iOS. If you load a journey from a local development server, add a `network_security_config.xml` that permits cleartext **only** for `10.0.2.2`, `localhost`, and `127.0.0.1`, ideally in a debug source set.

<Warning>Never ship `android:usesCleartextTraffic="true"` unscoped in production. Production journey URLs must be HTTPS. See the [Security Guide](/docs/go-v2/developer-integration/sdks/android/security).</Warning>

## Step 4: Initialize BridgeHost

Create a `BridgeHost` — this is the coordinator that routes messages between the WebView and your native code. In Compose, hold it in `remember { }` (or a ViewModel) so it survives recomposition; in the View system, keep it as a property on your Activity or Fragment.

```kotlin theme={null}
import com.gbg.gbgbridge.core.BridgeHost

val host = remember { BridgeHost(hostVersion = "1.0.0") }
```

`BridgeHost` is main-thread-only: state-mutating methods throw `IllegalStateException` when called off the main thread. From a coroutine, hop back with `withContext(Dispatchers.Main)` before touching the host.

<Warning>`host.delegate` is held via a `WeakReference` — the host does not keep your delegate alive. Store the delegate in a property with its own strong reference; an inline `host.delegate = MyDelegate()` will silently stop firing once the delegate is garbage-collected.</Warning>

## Step 5: Set up capture handlers

Attach handlers to the typed capability slots. Setting a handler declares that your app supports that capability — no separate configuration step.

### Option A: Your Own Capture Surface (Development / Early Integration)

Android ships no built-in stub views — your app supplies the capture UI. The handler pattern is to suspend until your UI completes the capture:

```kotlin theme={null}
host.documentCapture.handler = { request ->
  host.documentCapture.awaitCompletion()
}

host.selfieCapture.handler = { request ->
  host.selfieCapture.awaitCompletion()
}
```

Then present your capture surface when a request arrives by collecting the `activeRequest` state flow, and resolve the request from the UI:

```kotlin theme={null}
val documentRequest by host.documentCapture.activeRequest.collectAsState()

if (documentRequest != null) {
  DocumentCaptureScreen(
    onCaptured = { imageData, width, height ->
      host.documentCapture.complete(
        CaptureResult.Document(imageData, width, height)
      )
    },
    onCancelled = {
      host.documentCapture.cancelIfBusy("User dismissed")
    }
  )
}
```

<Note>See [Capture Screens](/docs/go-v2/developer-integration/sdks/android/capture-screens) for capture-UI patterns: a CameraX live preview, the system photo picker, and a placeholder bitmap for emulator development.</Note>

### Option B: Smart Capture SDKs (Production)

Smart Capture integration for Android is not yet available. The capture surface is designed to be swappable — the handler setup above stays identical, and only the UI presented when a request arrives changes.

## Step 6: Set permission state

Report camera permission state so the web journey can check permissions before attempting capture:

```kotlin theme={null}
val camera = CameraDetector.check(context)
host.documentCapture.permissionState = camera.permissionState
host.selfieCapture.permissionState = camera.permissionState
```

<Note>`CameraDetector` only distinguishes `GRANTED` from `NOT_DETERMINED` — Android cannot tell never-asked apart from permanently denied without app-side state. After running your own runtime-permission flow, set the richer `DENIED` state on the slots yourself.</Note>

## Step 7: Display the journey

There is no `BridgeWebView` wrapper on Android. Create a plain `WebView`, attach the host, and load the journey URL — `attach()` configures the WebView for you (enables JavaScript and DOM storage, installs the bootstrap-injecting WebViewClient and a default WebChromeClient, and registers the JavaScript interface).

```kotlin theme={null}
@Composable
fun JourneyView(host: BridgeHost, journeyUrl: String) {
  AndroidView(
    factory = { ctx ->
      WebView(ctx).also { webView ->
        host.attach(webView)
        webView.loadUrl(journeyUrl)
      }
    },
    modifier = Modifier.fillMaxSize()
  )

  DisposableEffect(Unit) {
    onDispose {
      host.documentCapture.cancelIfBusy("Screen closed")
      host.selfieCapture.cancelIfBusy("Screen closed")
      host.detach()
    }
  }
}
```

A few rules keep the bridge wiring intact:

* **Custom WebViewClient** — if you need your own navigation policy or error logging, subclass `BootstrapInjectingWebViewClient` and pass it via `host.attach(webView, client = ...)`. Setting a plain `WebViewClient` yourself would remove bootstrap injection; call `super.onPageStarted(...)` in your subclass to keep it.
* **Custom WebChromeClient** — set it *after* `attach()`. Attaching installs a default chrome client and overwrites whatever was there before.
* **SSL errors** — never call `handler.proceed()` in `onReceivedSslError`. Cancel the load and surface the failure instead.
* **Lifecycle** — call `detach()` when the screen goes away but the host may be reused, and `dispose()` for terminal teardown (Compose `DisposableEffect.onDispose`, Activity `onDestroy`, or ViewModel `onCleared`). After `dispose()` the host cannot be reused. `detach()` cancels in-flight typed-slot captures automatically, but cancel them explicitly first when you want to control the cancellation reason — and you must cancel manually if you registered raw handlers instead of the typed slots.

<Note>See [Embedding Guide](/docs/go-v2/developer-integration/sdks/android/embedding) for Compose and View-system integration patterns.</Note>

## Step 8: Build and Run

1. Build and run on a physical device for real camera capture; on the emulator, use the photo-picker or placeholder-bitmap paths from [Capture Screens](/docs/go-v2/developer-integration/sdks/android/capture-screens).
2. The web journey loads in the WebView.
3. When the journey reaches a document or selfie step, the bridge sends a capture request.
4. Your handler runs, your capture surface presents, the user captures, and the result flows back to the web journey.

If your journey URL comes from a local development server, remember that `localhost` on the emulator is the emulator itself — use `10.0.2.2` to reach your host machine, or run `adb reverse tcp:3000 tcp:3000`.

### Verify It Works

Check for these signs of a successful integration:

* The web journey loads and renders correctly in the WebView.
* `capability.query` is handled automatically — the journey knows which capabilities your app supports.
* Document capture requests trigger your capture surface, and the captured image is returned to the journey.
* Selfie capture requests trigger your selfie surface, and the result is returned.
* Cancellation flows work — dismissing the capture surface sends a `cancelled` response.

### Common Issues

| Symptom                                          | Likely Cause                                     | Fix                                                                                                         |
| ------------------------------------------------ | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| Blank WebView                                    | Cleartext HTTP blocked (API 28+)                 | Use HTTPS, or add a `network_security_config.xml` scoped to `10.0.2.2`/`localhost` for local dev            |
| Blank WebView on emulator with a `localhost` URL | `localhost` is the emulator itself               | Use `10.0.2.2`, or `adb reverse tcp:3000 tcp:3000`                                                          |
| `capability.query` returns empty                 | No handlers set                                  | Set `handler` on at least one typed slot before loading the URL                                             |
| Capture surface never appears                    | `activeRequest` not collected                    | Collect `host.documentCapture.activeRequest` (`collectAsState()`) and show your surface when it is non-null |
| Capture result not received by journey           | `complete()` not called                          | Ensure every exit path calls `complete(...)` or `cancelIfBusy(...)`                                         |
| Delegate callbacks stop firing                   | Delegate was garbage-collected (`WeakReference`) | Hold a strong reference to your delegate                                                                    |
| `IllegalStateException` from host methods        | Called off the main thread, or after `dispose()` | Call from the main thread (`withContext(Dispatchers.Main)`); never reuse a disposed host                    |

<Note>See [Troubleshooting](/docs/go-v2/developer-integration/sdks/android/troubleshooting) for a comprehensive diagnostic guide.</Note>

## Complete Minimal Example

Putting it all together — a minimal Compose screen that loads a journey and handles document capture with your own capture surface:

```kotlin theme={null}
import android.webkit.WebView
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.gbg.gbgbridge.capabilities.CameraDetector
import com.gbg.gbgbridge.capabilities.CaptureResult
import com.gbg.gbgbridge.core.BridgeHost

@Composable
fun JourneyScreen(journeyUrl: String) {
  val context = LocalContext.current
  val host = remember {
    BridgeHost(hostVersion = "1.0.0").apply {
      documentCapture.permissionState =
        CameraDetector.check(context).permissionState
      documentCapture.handler = { request ->
        documentCapture.awaitCompletion()
      }
    }
  }
  val documentRequest by host.documentCapture.activeRequest.collectAsState()

  AndroidView(
    factory = { ctx ->
      WebView(ctx).also { webView ->
        host.attach(webView)
        webView.loadUrl(journeyUrl)
      }
    },
    modifier = Modifier.fillMaxSize()
  )

  if (documentRequest != null) {
    // Your capture surface — CameraX preview, photo picker, or placeholder
    DocumentCaptureScreen(
      onCaptured = { imageData, width, height ->
        host.documentCapture.complete(
          CaptureResult.Document(imageData, width, height)
        )
      },
      onCancelled = {
        host.documentCapture.cancelIfBusy("User dismissed")
      }
    )
  }

  DisposableEffect(Unit) {
    onDispose {
      host.documentCapture.cancelIfBusy("Screen closed")
      try {
        host.dispose()
      } catch (e: Exception) {
        // The WebView may already be shutting down — log and move on
      }
    }
  }
}
```

`DocumentCaptureScreen` is your own composable — see [Capture Screens](/docs/go-v2/developer-integration/sdks/android/capture-screens) for ready-made patterns.

<Note>See [Hello Journey](/docs/go-v2/developer-integration/sdks/android/examples/hello-journey) for the full annotated example.</Note>

## What's Next

Once the basic integration is working:

* Add selfie capture — same pattern as document capture, using `host.selfieCapture`.
* Add custom capabilities — see [Capability Handling](/docs/go-v2/developer-integration/sdks/android/capability-handling).
* Build a production-quality capture surface with CameraX — see [Capture Screens](/docs/go-v2/developer-integration/sdks/android/capture-screens).
* Review the [Security Guide](/docs/go-v2/developer-integration/sdks/android/security) before shipping to production.
