Skip to main content
This guide walks you through adding GBGBridge to your Android project, configuring it, and loading your first web-based identity journey.

Requirements

RequirementMinimumNotes
Android (minSdk)API 24 (Android 7.0)Covers the vast majority of active devices
compileSdk / targetSdk34Your app should compile against API 34 or later
JDK17Required by the Android Gradle Plugin toolchain
Kotlin2.xThe SDK is built with Kotlin 2.2.10
ArtifactAARPublished to Maven Central
Unlike the iOS SDK, GBGBridge for Android is not zero-dependency. It depends on androidx.annotation and kotlinx-serialization-json, and exposes kotlinx-coroutines-android as an api dependency — coroutine types appear on the public surface of CaptureCapability, so your app needs coroutines available (which any modern Android project already has).

Installation

GBGBridge is published to Maven Central as com.gbg:gbgbridge-sdk. Add the dependency to your module’s build.gradle.kts:
dependencies {
  implementation("com.gbg:gbgbridge-sdk:0.1.0-alpha01")
}
No repository setup is needed beyond mavenCentral(), which most projects already declare in settings.gradle.kts:
dependencyResolutionManagement {
  repositories {
    google()
    mavenCentral()
  }
}
You can also add the dependency through the Android Studio UI (File > Project Structure… > Dependencies), but the Gradle snippet above is the canonical path. Sync the project and you’re ready to go.

Minimal Integration

The steps below take you from an empty screen to a running journey: create a BridgeHost, declare a capability, attach a WebView, and load the journey URL.

1. Add the Imports

import com.gbg.gbgbridge.core.BridgeHost
import com.gbg.gbgbridge.capabilities.CaptureResult
import com.gbg.gbgbridge.capabilities.CameraDetector

2. Initialize the Bridge Host

The BridgeHost manages message routing between the WebView and your native code. The simplest way to create one is with a host version string. In Compose, hold it in remember so it survives recomposition:
val host = remember { BridgeHost(hostVersion = "1.0.0") }
BridgeHost is a main-thread-only class — construct it and call its methods on the main thread.

3. Declare Capabilities via Typed Slots

BridgeHost exposes typed capability slots for well-known capture operations. Setting a handler on a slot declares support — no separate configuration step needed:
// Declare document capture support by setting a handler
host.documentCapture.handler = { request ->
  // Your capture logic here — return a CaptureResult
  CaptureResult.Cancelled(reason = "Not yet implemented")
}

// Optionally detect and report camera permission state
val camera = CameraDetector.check(context)
host.documentCapture.permissionState = camera.permissionState
CameraDetector.check(context) needs a Context — in Compose, use LocalContext.current. It reports GRANTED or NOT_DETERMINED; if your app runs its own permission flow, set the richer DENIED/RESTRICTED states on the slot yourself.

4. Display the Journey

There is no prebuilt WebView component on Android — you create a standard WebView and attach the host to it. attach() configures the WebView internally (JavaScript, DOM storage, bootstrap injection, message interface), so there is no separate configure step. In Compose, wrap the WebView in AndroidView:
@Composable
fun JourneyScreen(journeyUrl: String) {
  val host = remember { BridgeHost(hostVersion = "1.0.0") }

  AndroidView(
    modifier = Modifier.fillMaxSize(),
    factory = { context ->
      WebView(context).also { webView ->
        host.documentCapture.handler = { request ->
          host.documentCapture.awaitCompletion()
        }
        host.attach(webView)
        webView.loadUrl(journeyUrl)
      }
    }
  )

  DisposableEffect(Unit) {
    onDispose { host.detach() }
  }
}
Call host.attach(webView) before loadUrl() so the bootstrap script is in place when the page loads, and call host.detach() when the screen leaves composition. If you use the View system rather than Compose, the wiring is the same: create a WebView in your Activity or Fragment, attach, load, and call detach() (or dispose() for terminal teardown) in onDestroy().

5. Run Your App

Build and run. The web journey loads inside the native WebView. When the journey sends a capability.query request, GBGBridge automatically responds with the capabilities derived from your typed slots — including which are supported and their permission state.

Alternative: Configuration-Based Initialization

If you need explicit control over the capability map (e.g., for custom capabilities or static declarations), use the configuration-based constructor:
val configuration = BridgeConfiguration(
  hostVersion = "1.0.0",
  capabilities = mapOf(
    "camera.document" to BridgeCapabilityInfo(supported = true, version = "1.0"),
    "camera.selfie" to BridgeCapabilityInfo(supported = true, version = "1.0")
  )
)

val host = BridgeHost(configuration = configuration)
Android additionally accepts an optional capabilitiesProvider lambda as a second constructor argument. It is re-evaluated on every capability read, so you can reflect dynamic state (such as permission changes) without polling — this replaces the mutable capability map the iOS SDK exposes. Both initialization paths support register(handler) for custom BridgeCapabilityHandler implementations. See the Capability Handling Guide for details.

What Happens Under the Hood

When you call host.attach(webView), several things happen automatically:
  1. WebView configuration: JavaScript and DOM storage are enabled, and a bootstrap-injecting WebViewClient plus a plain WebChromeClient are installed. This overwrites any clients you set earlier — if you need a custom WebChromeClient, set it after attach(); for a custom WebViewClient, subclass BootstrapInjectingWebViewClient and pass it via attach(webView, client = ...).
  2. Bootstrap script injection: A JavaScript snippet is evaluated in onPageStarted (main frame only) that initializes window.GBGBridge with a receive() function.
  3. JavaScript interface registration: The host is exposed to the page via addJavascriptInterface under the name GBGBridge. Web content sends messages to native via window.GBGBridge.postMessage(jsonString) — note this differs from iOS, where the channel is window.webkit.messageHandlers.gbgBridge.
  4. Capability query handler: A built-in handler for the capability.query action is registered. The query response is built dynamically from typed slots and custom capabilities, including permission state metadata.
Bootstrap timing diverges from iOS. On iOS the bootstrap is injected via WKUserScript at document start, guaranteed to run before any page JavaScript. On Android, injection happens in onPageStarted on a best-effort basis — a head script that synchronously calls window.GBGBridge.receive could race the injection. Keep this in mind if your web content interacts with the bridge during initial page load.

Required App Configuration

GBGBridge itself does not force any entries into your app — the library manifest declares nothing. However, depending on the capabilities you expose, your host app will need appropriate manifest entries:
<!-- Always required: the journey loads over the network -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Only if you host camera capture -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature
  android:name="android.hardware.camera"
  android:required="false" />
Declaring the camera feature as android:required="false" keeps your app installable on devices without a camera; check availability at runtime with CameraDetector.check(context).

Local development over HTTP

Android blocks cleartext HTTP traffic by default (API 28+). If your journey URL points at a local development server (e.g., http://10.0.2.2:3000), add a network security config that permits cleartext for development hosts only. Create res/xml/network_security_config.xml:
<network-security-config>
  <base-config cleartextTrafficPermitted="false" />
  <domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="false">10.0.2.2</domain>
    <domain includeSubdomains="false">localhost</domain>
    <domain includeSubdomains="false">127.0.0.1</domain>
  </domain-config>
</network-security-config>
Then reference it from the <application> element in your manifest:
<application
  android:networkSecurityConfig="@xml/network_security_config"
  ... >
For production, always use HTTPS. Never ship android:usesCleartextTraffic="true" unscoped — keep cleartext exceptions limited to local development hosts (or to a debug source set override, so release builds stay locked down).

Common Integration Pitfalls

A handful of issues catch most teams when first wiring up the bridge — wrong WebView setup, missing capability declarations, cleartext policy rejecting non-HTTPS dev URLs, and malformed messages on the web side. The notes below explain what to check.

WebView not receiving messages

Ensure you call host.attach(webView) before loadUrl() — a plain WebView without attach() has no bootstrap script and no GBGBridge JavaScript interface. Also avoid replacing the WebViewClient after attaching: that removes the bootstrap injection. If you need custom navigation handling, subclass BootstrapInjectingWebViewClient and pass it to attach(webView, client = ...), calling super.onPageStarted to keep the bootstrap.

Capability query returns empty

If using BridgeHost(hostVersion = ...): check that you set a handler on at least one typed slot, or registered a custom capability via registerCustomCapability(). If using BridgeHost(configuration = ...): check that you passed capabilities in your BridgeConfiguration (or supplied a capabilitiesProvider).

Cleartext HTTP blocked

If your journey URL uses http:// and the page fails to load (typically net::ERR_CLEARTEXT_NOT_PERMITTED), add the network security config shown above. On the emulator, remember that your host machine is 10.0.2.2 — localhost refers to the emulator itself. Alternatively, forward the port with adb reverse tcp:3000 tcp:3000 and use http://localhost:3000.

Messages not decoding

GBGBridge expects messages in the Bridge Message Protocol format. If you see decode errors in host.lastError (or via delegate.onError), verify that the web content is sending correctly structured JSON.

Next Steps