Skip to main content
This example demonstrates bidirectional messaging between the native Android host and the web journey: sending events to the web journey and handling incoming requests with capability handlers.

What this example demonstrates

  • Sending events from native to web
  • Declaring capture support via typed capability slots
  • Registering custom capabilities for non-camera actions
  • Responding with success, error, and cancellation statuses
  • Observing all bridge traffic via BridgeHostDelegate

Complete source

Code explanation

The sections below cover what each part of the app does, in roughly the order they’re exercised at runtime.

Outgoing events (native β†’ web)

The web journey receives this via window.GBGBridge.receive() β€” the same function on both platforms β€” and can use the timestamp and version information to initialize its state. Event data is a Map<String, JsonElement>; build values with JsonPrimitive(...) or buildJsonObject { } from kotlinx.serialization.
An event sent before the journey page has loaded won’t be observed by the web side β€” the bootstrap that installs window.GBGBridge.receive is injected as the page starts loading. The delegate still records the send intent via onMessageSent, which fires for every outbound message before transport.

Incoming requests (web β†’ native)

The web journey sends requests to the native host via window.GBGBridge.postMessage(jsonString) β€” the JavaScript interface the SDK installs under the name GBGBridge. This is the load-bearing platform difference: on iOS the channel is window.webkit.messageHandlers.gbgBridge. Web code targeting both platforms should feature-detect:
When the web journey sends a camera.document.capture request, the typed slot handler runs:
The CaptureResult is automatically encoded to the bridge protocol format β€” no manual JsonElement map construction needed. The typed slot handler is a suspend lambda running on the main dispatcher, so you can delay, hop dispatchers for heavy work, or suspend on awaitCompletion() while a capture UI is showing. The device.info custom capability shows the other path: registerCustomCapability takes a synchronous handler (BridgeMessage, BridgeResponder) -> Unit. The example responds inline; for asynchronous work, retain the responder, do the work, hop back to the main thread, and call responder.respond(...) exactly once.

Holding the delegate

BridgeHost.delegate is backed by a WeakReference β€” the host never keeps your delegate alive. This is why the example routes everything through a BridgeController held in remember { }: the controller strongly holds the MessageLogger, so it survives garbage collection. A naive port that writes host.delegate = MessageLogger() with no other reference compiles and works β€” until the next GC, after which callbacks silently stop firing. The logger also implements onMessageSent, an Android-only delegate method with no iOS equivalent: it fires for every outbound message (before transport), which is why the log panel shows OUT entries without any extra wiring.

Unhandled Requests

The MessageLogger delegate catches requests with no registered handler and responds with UNSUPPORTED. This prevents the web journey from waiting indefinitely for a response. Unhandled requests are appended to host.pendingRequests before onUnhandledRequest fires, so the lookup overload respond(to = correlationId, status, ...) finds and removes the matching pending request. You don’t have to respond inside the callback β€” you can let requests accumulate in pendingRequests and respond later (for example, after showing UI), using the same lookup overload. If no pending request matches the correlation ID, the call silently no-ops.

Running This Example

  1. Add GBGBridge to your module: implementation("com.gbg:gbgbridge-sdk:0.1.0-alpha01").
  2. Create a new empty Compose Activity project and register TwoWayActivity as the launcher activity in your manifest, alongside <uses-permission android:name="android.permission.INTERNET" />.
  3. Replace the generated activity code with the code above.
  4. Update JOURNEY_URL to point to your web journey.
  5. Build and run.

Next Steps