Jetpack Compose Integration
Unlike iOS, there is no prebuiltBridgeWebView composable. The Android pattern is three steps: construct a WebView inside an AndroidView factory, call host.attach(webView), then loadUrl(...). attach() configures the WebView internally (JavaScript, DOM storage, the bootstrap-injecting WebViewClient), so there is no separate setup call.
Attaching a WebView in Compose
Hold theBridgeHost in remember { } so it survives recomposition, attach it in the AndroidView factory, and detach in DisposableEffect.onDispose.
Observing Bridge State
BridgeHost is not observable — there is no ObservableObject analogue, and reading properties like lastError does not trigger recomposition. To drive Compose UI from bridge activity, mirror state into Compose via a BridgeHostDelegate. (The typed capture slots are the exception: host.documentCapture.activeRequest is a StateFlow, so you can collectAsState() it directly.)
Passing the Host to Child Views
BridgeHost is a plain class, so pass it to child composables as an ordinary parameter — no property-wrapper ceremony is needed. The flip side: properties like receivedMessages are immutable snapshots per read and won’t recompose your UI when they change, so mirror anything you want to display through the delegate. When rendering message lists, key items by correlationId (BridgeMessage has no id property on Android).
Dynamic URL Changes
There is no URL binding to update. TheAndroidView factory runs once, so navigate to a new URL from the update block (which runs on each recomposition) by calling loadUrl(...) when your URL state changes. The bridge survives navigation — the bootstrap script is re-injected on every page load.
View System Integration
For Activity- or Fragment-based apps, the pattern is identical — only the WebView construction differs. There is nomakeWebView factory on Android: create the WebView yourself (programmatically or inflated from XML), then call host.attach(webView) and loadUrl(...).
Attaching in an Activity
Hold the host as a property so it lives as long as the Activity, attach inonCreate, and tear down in onDestroy.
What attach() Configures
attach() calls BridgeWebViewConfigurator.configure() internally: it enables javaScriptEnabled and domStorageEnabled, installs the bootstrap-injecting WebViewClient, and installs a plain WebChromeClient — unconditionally overwriting any clients already set on the WebView. Three practical consequences:
- Don’t call
configure()separately beforeattach()— it would be re-run and clobbered byattach()’s own configure pass. - Set a custom
WebChromeClientafterattach(), never before, or the plain one installed byconfigure()will overwrite yours. - Other WebView settings are untouched. If you need additional settings (media playback, caching, etc.), apply them on the WebView yourself —
attach()won’t reset them.
Lifecycle Considerations
TheBridgeHost and the WebView have separate lifecycles, the delegate is weakly held, and handlers should be set up before content loads. Keep these in mind to avoid leaks, silent callback loss, and missed messages.
Host Lifetime
TheBridgeHost should live at least as long as the WebView. In Compose, use remember { } (scoped to the screen) so the host isn’t recreated on recomposition. In the View system, hold a strong reference as an Activity property or in a ViewModel. Whatever owns the host should also strongly hold the delegate and any handler objects, since the host won’t keep them alive.
WebView Detachment
Callingdetach() removes the JavaScript interface, cancels any in-flight typed-slot capture (via cancelIfBusy), and clears pendingRequests and receivedMessages — unlike iOS, which preserves the message buffers across detach. Clearing avoids ghost entries in Compose lists when you re-attach. detach() is idempotent and safe to call from onDispose.
If you call respond(...) or sendEvent(...) while no WebView is attached, nothing throws: the host fires delegate.onMessageSent (so you can trace the intent) and then silently drops the message at the transport. This diverges from iOS, which records lastError = "WebView not attached" — on Android no lastError is set for this case.
If you registered raw BridgeCapabilityHandlers that hold on to a pending BridgeResponder, cancel or respond to them yourself before detaching so the web journey isn’t left waiting on a response that will never arrive:
Re-attaching a WebView
If you need to replace the WebView (e.g., after a navigation reset), calldetach() and then attach() with the new WebView. Inbound messages still in flight from the old attach session are identity-gated and dropped, so the new session starts clean.
Disposing the Host
dispose() is an Android-only addition (iOS relies on ARC for teardown). It performs a terminal teardown: detach plus cancellation of the typed capture slots’ coroutine scopes. After dispose(), state-mutating methods (attach, register, respond, sendEvent, and friends) throw IllegalStateException; detach(), dispose(), clearError(), the getters, and the delegate setter remain safe. Call it from a terminal lifecycle callback — Activity.onDestroy or ViewModel.onCleared — wrapped in try/catch with a log, since removeJavascriptInterface can throw on a WebView that is already shutting down.
detach() for a recoverable teardown (the screen may come back and re-attach) and dispose() only when the host will never be used again.
Setting Up Handlers
Set handlers on typed slots or register custom capabilities before the WebView loads content. If the web journey sends a request before a handler is available, the request goes topendingRequests (and delegate.onUnhandledRequest fires) — you can still answer it later via the lookup overload of respond(...).
Custom WebViewClient
On Android, navigation policy lives in theWebViewClient — and the bridge depends on the one attach() installs, because it injects the bootstrap script on every page load. If you need custom navigation behavior (intercepting links, logging load errors, SSL handling), don’t replace the client wholesale: subclass BridgeWebViewConfigurator.BootstrapInjectingWebViewClient and pass your instance to attach(webView, client = ...). Override whatever callbacks you need; if you override onPageStarted, call super.onPageStarted(...) to keep the bootstrap injection.
Two things to know when supplying your own client:
- The
bootstrapScriptparameter onconfigure()is ignored when aclientis passed — your subclass owns its script, so pass the script to the subclass constructor. The SDK’s default bootstrap literal is internal, so supply it yourself. - The bootstrap injects on the main frame only, on every page load.
Bootstrap timing diverges from iOS. iOS injects the bootstrap via
WKUserScript at .atDocumentStart, guaranteed to run before any page JavaScript. Android injects it via evaluateJavascript from onPageStarted, which is best-effort: a <script> in the document head that synchronously calls window.GBGBridge.receive could race and find it undefined. Web code should not assume the bootstrap is present before its own head scripts run.Next Steps
- Messaging Guide — Learn to send events and handle requests
- Capability Handling Guide — Register handlers for native features
- API Reference — Detailed reference for
BridgeHost.attachandBridgeWebViewConfigurator