By Rahul — Google Frontend Engineer
Why This Matters
Many apps embed web content inside native apps using WebView. The web page needs to talk to the native app (access camera, GPS, file system) and the native app needs to send data to the web page. Getting this communication right is critical for hybrid apps.
JavaScript Bridge (Android)
Android's WebView lets you inject a JavaScript interface:
// Android (Kotlin) — expose native methods to JS
class NativeBridge {
@JavascriptInterface
fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
@JavascriptInterface
fun getDeviceId(): String {
return Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
}
}
webView.addJavascriptInterface(NativeBridge(), "Android")
// JavaScript — call native methods
window.Android.showToast("Hello from web!");
const deviceId = window.Android.getDeviceId();WKScriptMessageHandler (iOS)
// Swift — receive messages from JS
class MessageHandler: NSObject, WKScriptMessageHandler {
func userContentController(_ controller: WKUserContentController,
didReceive message: WKScriptMessage) {
if message.name == "nativeAction" {
let body = message.body as? [String: Any]
handleAction(body)
}
}
}
// JavaScript — send to native
window.webkit.messageHandlers.nativeAction.postMessage({
action: "share",
data: { url: "https://example.com", title: "Check this out" }
});URL Scheme Interception
The oldest and most universal method. Web page navigates to a custom URL, native app intercepts it:
// JavaScript
window.location.href = "myapp://action?param=value";
// Or using an iframe (non-disruptive)
const iframe = document.createElement("iframe");
iframe.src = "myapp://getLocation";
document.body.appendChild(iframe);
setTimeout(() => iframe.remove(), 100);postMessage Pattern
// Web → Native: post a message
window.postMessage(JSON.stringify({
type: "REQUEST_LOCATION",
requestId: "abc123"
}), "*");
// Native → Web: evaluate JavaScript
webView.evaluateJavaScript(
"window.dispatchEvent(new CustomEvent('nativeResponse', { detail: { lat: 37.7, lng: -122.4, requestId: 'abc123' } }))"
);
// Web listens for native responses
window.addEventListener("nativeResponse", (e) => {
console.log(e.detail); // { lat: 37.7, lng: -122.4 }
});Production Best Practices
- Always check if the bridge exists:
if (window.Android)before calling - Use a message queue for async responses — match request/response with IDs
- Validate all data coming from the bridge — never trust it blindly
- Version your bridge API — old app versions may not have new methods
- Handle the case where the page runs outside WebView (regular browser)
Security Concerns
- JavaScript bridges expose native functionality to web content — restrict which URLs can use the bridge
- Never expose sensitive methods (file access, contacts) without user confirmation
- On Android,
@JavascriptInterfacemethods are accessible to any JS on the page, including injected scripts
Summary
WebView-native communication uses JavaScript bridges (Android), message handlers (iOS), URL scheme interception, or postMessage. Always version your bridge API, validate data, and handle the non-WebView case gracefully.