Skip to content

JavaScript Bridge

One of the most powerful features of compose-webview is its Promise-based JavaScript Bridge. It simplifies communication between your Kotlin code and the JavaScript running inside the WebView.

Mobile First

The JSBridge is highly optimized for Android and iOS, providing a unified API. Support for Web and Desktop is currently experimental.


Concept

Traditional JavascriptInterface (Android) or WKScriptMessageHandler (iOS) often leads to callback hell or requires complex JSON parsing manually.

Our bridge solves this by:

  1. Promises: All calls from JS return a Promise, allowing await syntax.
  2. Serialization: Automatic JSON conversion for arguments and return values.
  3. Type Safety: Define your data models in Kotlin once.

Setup

1. Define Data Models

Use @Serializable from kotlinx.serialization to define the data structures you want to pass.

@Serializable
data class User(val id: String, val name: String)

@Serializable
data class DeviceInfo(val model: String, val osVersion: String)

2. Create the Bridge

Create a bridge instance using rememberWebViewJsBridge. You can optionally customize the JavaScript object name (default is window.AppBridge).

val bridge = rememberWebViewJsBridge(
    jsObjectName = "MyApp" // JS will access via window.MyApp
)

Receiving Calls from JavaScript

Use the register function to define handlers for JavaScript calls.

Specify the input type T and output type R. The library handles JSON parsing automatically.

LaunchedEffect(bridge) {
    // JS: await window.MyApp.call('getUser', { id: '123' })
    bridge.register<UserRequest, User>("getUser") { request ->
        // This block runs in a coroutine
        val user = userRepository.findById(request.id)
        user // Returned value is sent back to JS
    }
}

No Return Value

If your handler doesn't return anything (void), use Unit as the return type.

// JS: await window.MyApp.call('log', 'Hello')
bridge.register<String, Unit>("log") { message ->
    Log.d("WebView", "JS Log: $message")
}

No-Argument Handler

For no-argument calls, you can use either register<Unit, R> or register<R>.

bridge.register<Unit, Unit>("refreshSession") {
    Log.d("WebView", "Refreshing session...")
}

bridge.register<Unit>("refreshSessionAlt") {
    Log.d("WebView", "Refreshing session...")
}

Both JavaScript forms are valid:

await window.AppBridge.call('refreshSession');
await window.AppBridge.call('refreshSession', null);

register<T, R> null input rules: - T is Unit: null is accepted. - Other non-null input types: null throws an input type error.

Nullable Payload Handler

Use registerNullable<T, R> when JavaScript may send null for a typed payload.

bridge.registerNullable<UserRequest, User>("getUserMaybe") { requestOrNull ->
    if (requestOrNull == null) {
        User(id = "0", name = "Guest")
    } else {
        userRepository.findById(requestOrNull.id)
    }
}

Calling JavaScript from Kotlin

You can also send events or execute arbitrary JavaScript.

Emitting Events

You can "emit" events that JavaScript can listen to.

Kotlin:

bridge.emit("onNetworkStatusChange", NetworkStatus(online = true))

JavaScript:

window.MyApp.on('onNetworkStatusChange', (status) => {
    console.log("Is Online:", status.online);
});

Evaluating JavaScript

Execute raw JavaScript code.

controller.evaluateJavascript("alert('Hello from Kotlin!')") { result ->
    println("Result: $result")
}

JavaScript API Reference

The bridge injects a global object (default AppBridge) into the WebView.

call(handlerName, data)

Calls a native handler registered in Kotlin. Returns a Promise.

try {
    const result = await window.AppBridge.call('getUser', { id: 1 });
    console.log(result);
} catch (error) {
    console.error("Native error:", error);
}

on(eventName, callback)

Subscribes to an event emitted from Kotlin.

const onEvent = (data) => {
    // Handle event
};

window.AppBridge.on('eventName', onEvent);

// Later
window.AppBridge.off('eventName', onEvent);

Custom JSON Serializer

By default, the library uses kotlinx.serialization. If you prefer Gson, Moshi, or Jackson, you can implement the BridgeSerializer interface.

import kotlin.reflect.KType

class CustomSerializer : BridgeSerializer {
    override fun encode(data: Any?, type: KType): String =
        TODO("Serialize data using your JSON library and KType")

    override fun <T> decode(json: String, type: KType): T =
        TODO("Deserialize json using your JSON library and KType")
}

// Usage
val bridge = rememberWebViewJsBridge(serializer = CustomSerializer())