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

# FAQ

> Frequently asked questions about the GBGBridge Android SDK.

## General

Common questions about supported platforms, dependencies, and how the SDK is distributed.

### What Android versions does GBGBridge support?

Android 7.0 (API 24) and later. The SDK is compiled against compile/target SDK 34.

### Does GBGBridge have any external dependencies?

A small set: `androidx.annotation`, `kotlinx-serialization-json`, and `kotlinx-coroutines-android`. Coroutines is exposed as an `api` dependency because coroutine types appear on the public surface of `CaptureCapability` (suspend handlers, `StateFlow`); the others are implementation details. Unlike the iOS SDK, the dependency count is not zero — but all three are standard AndroidX/Kotlin libraries most apps already include.

### What Kotlin version is required?

Kotlin 2.x and JDK 17. The SDK is built with Kotlin 2.2.10.

### How is GBGBridge distributed?

As an AAR via [Maven Central](https://central.sonatype.com/artifact/com.gbg/gbgbridge-sdk). No repository setup is needed beyond `mavenCentral()`:

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

### Does it work on the emulator?

Yes. The emulator provides a synthetic camera image to camera APIs, and the [reference app](https://github.com/gbgplc/gbg-go-android-reference)'s capture surface includes a placeholder-bitmap path that needs no camera at all. For networking to a local journey server, use `10.0.2.2` (the host machine as seen from the emulator — `localhost` is the emulator itself), or run `adb reverse tcp:3000 tcp:3000`.

## Integration

Questions about wiring GBGBridge into your app's UI layer and capability setup.

### Does GBGBridge work with both Jetpack Compose and the View system?

Yes. The SDK has no Compose dependency — integration is a plain `WebView` plus `host.attach(webView)`. In Compose, create the WebView inside `AndroidView(factory = { WebView(it) })`; in the View system, create or inflate the WebView in your Activity or Fragment. See the [Embedding Guide](/docs/go-v2/developer-integration/sdks/android/embedding) for details.

### Can I use multiple BridgeHost instances?

Each `BridgeHost` should be associated with one `WebView`. If you have multiple WebViews (e.g., multiple journey tabs), create a separate `BridgeHost` for each.

### Can I use my own WebViewClient?

Yes. Subclass `BootstrapInjectingWebViewClient` (it is `public open`), add your navigation policy or error logging, and pass it to `attach`:

```kotlin theme={null}
class MyWebViewClient(bootstrapScript: String) :
  BootstrapInjectingWebViewClient(bootstrapScript) {
  override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
    return !isAllowedHost(request.url)
  }
}

host.attach(webView, client = MyWebViewClient(bootstrap))
```

If you override `onPageStarted`, call `super.onPageStarted(...)` so the bootstrap script is still injected.

### Can I change capabilities after initialization?

With typed slots (`BridgeHost(hostVersion)`), capabilities are inherently dynamic. Set or clear `handler` to change support, toggle `isEnabled` to temporarily disable, and update `permissionState` as permissions change. The capability query response is built dynamically on each query.

With the configuration-based constructor, the `capabilities` property is a read-only merged snapshot (unlike iOS, where the map is mutable). For dynamic capabilities in that path, pass a `capabilitiesProvider` lambda — an Android-only addition that is re-evaluated every time capabilities are read or queried:

```kotlin theme={null}
val host = BridgeHost(
  configuration = BridgeConfiguration(hostVersion = "1.0.0"),
  capabilitiesProvider = { currentCapabilities() }
)
```

### Do I need to handle the `capability.query` action myself?

No. `BridgeHost` automatically registers a `CapabilityQueryHandler` that responds to `capability.query` requests, built from the typed slots, custom capabilities, and the static configuration map or `capabilitiesProvider`. The handler class is public, so if you need different behavior you can `unregister("capability.query")` and register a replacement.

### What is the difference between typed slots and custom capabilities?

**Typed slots** (`host.documentCapture`, `host.selfieCapture`) are built-in `CaptureCapability` properties for well-known capture operations. They handle result encoding, busy rejection, and permission state automatically. Handlers are suspend lambdas that return `CaptureResult` values.

**Custom capabilities** (`registerCustomCapability()`) support any action. Handlers receive a `BridgeResponder` and build responses manually using `JsonElement` maps.

Both appear in `capability.query` responses. If both are registered for the same ID, the typed slot takes precedence — but only when its handler is set; an unused slot never shadows a custom registration.

### How does permission state work?

Each typed slot has a `permissionState` property (default: `NOT_DETERMINED`). Populate it using `CameraDetector.check(context)`:

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

This is included in the `capability.query` response as a `permissionState` field, allowing the web journey to detect permission issues before attempting capture.

<Note>
  `CameraDetector` only reports `GRANTED` or `NOT_DETERMINED` — Android cannot distinguish "never asked" from "permanently denied" without app-side state. After running your own permission flow, set the richer `DENIED` or `RESTRICTED` values on the slot yourself.
</Note>

### What happens if the web journey sends a request for an action I haven't registered?

The request is added to `host.pendingRequests` and `onUnhandledRequest(host, request)` is called on the delegate. You can respond to it manually via the lookup overload `host.respond(to = correlationId, status = ..., data = ...)`.

If you don't respond, the request sits in `pendingRequests` indefinitely. The web journey may implement its own timeout.

## Messaging

Questions about message delivery, ordering, and the wire protocol.

### Is the wire protocol the same as the iOS SDK?

Yes. The message envelope (`version`, `correlationId`, `type`, `timestamp`, `payload`), response statuses, and the `capability.query` response shape are identical — the `environment` field reports `"android"` instead of `"ios"`. The one platform difference is the web-to-native entry point: Android exposes `window.GBGBridge.postMessage(jsonString)`, while iOS uses `window.webkit.messageHandlers.gbgBridge.postMessage(...)`. Web code targeting both platforms must feature-detect which is present. The native-to-web direction (`window.GBGBridge.receive`) is identical on both.

### Are messages guaranteed to be delivered?

Messages sent via the bridge are in-process calls through the WebView. They are delivered reliably as long as:

* The WebView is attached to the host.
* The web page has not navigated away.
* `window.GBGBridge.receive` is defined on the web side.

There is no built-in retry or delivery confirmation mechanism. Note one Android-specific behavior: calling `respond()` or `sendEvent()` while no WebView is attached still fires the delegate's `onMessageSent` (as an intent trace), but the message is silently dropped at the transport — no `lastError` is recorded, unlike iOS.

### What is the maximum message size?

There is no hard limit imposed by GBGBridge. However, very large messages (e.g., multi-megabyte Base64 images) can cause memory pressure. For large data transfers, consider passing file paths instead of inline data.

### Can I send messages before the web page loads?

Messages sent before the page loads (or before the web journey sets up its `receive()` function) will be lost. The default bootstrap script creates a no-op `receive()`, so the call won't error — but the data won't be processed.

<Tip>
  Wait for a signal from the web journey (e.g., a `journey.ready` event) before sending messages.
</Tip>

### Is the message order preserved?

Messages are processed in the order they arrive: inbound messages come in on the WebView render thread and are posted to the main looper in sequence. Responses are sent in the order `respond()` is called, and `evaluateJavascript` calls execute sequentially.

### What happens to events — does anyone respond to them?

Events are fire-and-forget. They have no expected response, and they don't enter the request dispatch path — observe them in the delegate's `onMessage`. Both the native host and the web journey can send events. Use events for notifications, state updates, and lifecycle signals.

## Capabilities

Questions about declaring capabilities and handling hardware availability.

### How does the web journey know what capabilities are available?

The web journey sends a `capability.query` request. The built-in handler responds with the environment (`"android"`), host version, and a map of all declared capabilities with their `supported` status, `version`, and optionally `permissionState`.

### What if a capability is hardware-dependent?

For camera capabilities, use `CameraDetector.check(context)` and conditionally set the handler:

```kotlin theme={null}
val camera = CameraDetector.check(context)
if (camera.hardwareAvailable) {
  host.documentCapture.handler = { request ->
    // Show capture UI and await result
  }
  host.documentCapture.permissionState = camera.permissionState
}
```

For other hardware-dependent features exposed via `registerCustomCapability()`, run your availability check first and only register the capability when the hardware is present — capabilities that are never declared simply don't appear as supported in the query response.

### What manifest entries do I need?

GBGBridge's own library manifest forces nothing on your app. Your host app needs:

1. `<uses-permission android:name="android.permission.INTERNET" />` — always.
2. If hosting camera capture: `<uses-permission android:name="android.permission.CAMERA" />` plus `<uses-feature android:name="android.hardware.camera" android:required="false" />`, and request the runtime `CAMERA` permission at the point of use.

## Security

Questions about the trust boundary between the host app and the web journey.

### Is communication between the host and web journey encrypted?

Communication uses the WebView's JavaScript interface and `evaluateJavascript` within your app's process. It does not traverse the network, so TLS is not applicable. Messages are memory-to-memory within the device.

### Can other apps intercept bridge messages?

No. The WebView runs within your app's sandbox, and the `GBGBridge` JavaScript interface is private to your app process. To additionally guard against messages arriving from an unexpected page origin (e.g., after a rogue navigation), you can set `allowedOrigins` on `BridgeConfiguration` — an opt-in, Android-only message-level origin gate. Treat it as defence-in-depth, not a security boundary: pair it with navigation-level filtering in `shouldOverrideUrlLoading`.

### Should I validate incoming message data?

Yes. Treat data from the web journey the same way you would treat user input. Validate required fields, check value ranges, and reject malformed requests.

## Debugging

Questions about observing bridge traffic and diagnosing common issues.

### How do I see what messages are being exchanged?

Use the `BridgeHostDelegate` to log traffic in both directions — `onMessageSent` is an Android-only callback that fires for every outbound message:

```kotlin theme={null}
class LoggingDelegate : BridgeHostDelegate {
  override fun onMessage(host: BridgeHost, message: BridgeMessage) {
    Log.d("Bridge", "in  [${message.type}] ${message.payload.action}")
  }
  override fun onMessageSent(host: BridgeHost, message: BridgeMessage) {
    Log.d("Bridge", "out [${message.type}] ${message.payload.action}")
  }
}
```

You can also read the `host.receivedMessages` and `host.pendingRequests` snapshots at any time.

### Can I inspect the WebView?

Yes. Call `WebView.setWebContentsDebuggingEnabled(true)` (gate it on debuggable builds), then open `chrome://inspect` in desktop Chrome and inspect the WebView. You can examine `window.GBGBridge` and test messages from the console. To surface page console output in logcat, set a `WebChromeClient` with an `onConsoleMessage` override — after calling `attach()`, since attach installs its own clients.

### Why is my delegate not firing?

`BridgeHost.delegate` is backed by a weak reference — the host does not keep your delegate alive. An inline assignment like `host.delegate = LoggingDelegate()` with no other strong reference will be garbage collected and silently stop firing. Hold the delegate in a property whose lifetime matches the host (e.g., on your controller or ViewModel).

## Next Steps

Continue with these guides for deeper coverage of the topics above.

* [Getting Started](/docs/go-v2/developer-integration/sdks/android/getting-started) — First integration walkthrough
* [Troubleshooting](/docs/go-v2/developer-integration/sdks/android/troubleshooting) — Detailed issue resolution
* [API Reference](/docs/go-v2/developer-integration/sdks/android/api-reference) — Complete type reference
