cmp-webview

Ready-to-ship automation for library releases: converts README into a homepage, publishes API docs and interactive browser demo, generates downloadable sample apps, and wires full CI/CD.

Android JVMJVMKotlin/NativeWasm
GitHub stars3
Dependents1
OSS Health
LicenseMIT License
Creation date9 months ago

Last activity24 days ago
Latest release0.0.4 (24 days ago)

CMP WebView - Compose Multiplatform WebView Library

Compose Multiplatform Version Kotlin Version

Release workflow status Maven Central Version MIT License

Android iOS Desktop Web

A WebView library for Compose Multiplatform that enables you to display web content and HTML across Android, iOS, Desktop (JVM), and Web (WASM) platforms with a unified API.

Key Features:

  • 🌐 Load remote URLs (custom headers: Android only)
  • 📄 Display HTML content directly
  • 🔗 JavaScript → Compose messaging (via onScriptResult)
  • 🔁 Compose → JavaScript calls (via WebViewController.evaluateJavaScript)
  • 🔒 Built-in security protections against dangerous URL schemes
  • ♿ Accessibility support with screen reader compatibility
  • 🧪 Testing support with test tags and semantics
  • 🎯 Single API across all platforms (some capabilities are platform-dependent)

✅ Platform capability matrix

Capability Android iOS Desktop (JVM) Web (WASM)
Load remote url (iframe; subject to CSP/X-Frame-Options)
Load htmlContent
Custom request headers (headers)
JS → Compose (onScriptResult) with htmlContent
JS → Compose (onScriptResult) with remote url (bridge injected) (bridge injected) (bridge injected) ⚠️ best-effort (no cross-origin injection)
Compose → JS (WebViewController.evaluateJavaScript) (executes; no return values yet) (same-origin / htmlContent only)
evaluateJavaScript return values (returns Unsupported) (same-origin / htmlContent only)

Notes:

  • On Web/WASM, browsers prevent injecting scripts into cross-origin iframes. For reliable messaging on WASM, prefer htmlContent or same-origin pages you control.
  • On native targets, bridge injection can still be affected by page security policies (e.g., strict CSP). Treat messaging as best-effort for arbitrary third-party pages.
  • On Desktop/JVM, JavaScript execution is best-effort, but evaluateJavaScript currently does not surface return values (it returns WebViewJsResult.Unsupported(...)).

Wasm Demo: Try Live Demo

API Docs: Library API Docs


📦 Installation

Add the dependency to your Compose Multiplatform project:

Option A — Version catalog (recommended)

  1. In gradle/libs.versions.toml:
[versions]
cmpWebview = "0.0.1"

[libraries]
cmp-webview = { module = "io.github.aryapreetam:cmp-webview", version.ref = "cmpWebview" }
  1. In your module's build.gradle.kts:
kotlin {
  sourceSets {
    val commonMain by getting {
      dependencies {
        implementation(libs.cmp.webview)
      }
    }
  }
}

Option B — Direct dependency

kotlin {
  sourceSets {
    val commonMain by getting {
      dependencies {
        implementation("io.github.aryapreetam:cmp-webview:0.0.1")
      }
    }
  }
}

🚀 Quick Start

Load a Remote URL

@Composable
fun MyScreen() {
  WebView(
    url = "https://example.com",
    modifier = Modifier.fillMaxSize(),
    onLoadStarted = { println("Loading started") },
    onLoadFinished = { println("Loading finished") },
    onLoadError = { error -> println("Error: $error") }
  )
}

Display HTML Content

@Composable
fun HtmlScreen() {
  val html = """
        <html>
            <body>
                <h1>Hello from HTML!</h1>
                <p>This is rendered locally.</p>
            </body>
        </html>
    """.trimIndent()

  WebView(
    htmlContent = html,
    modifier = Modifier.fillMaxSize()
  )
}

JavaScript Bridge Communication

JavaScript code in your web content can communicate with your Compose code:

In your HTML/JavaScript:

// Listen for bridge ready event
window.addEventListener('ComposeWebViewBridgeReady', function () {
  // Send message to Compose
  ComposeWebViewBridge.postMessage('Hello from JavaScript!');
});

// Example: Send button click data
document.getElementById('myButton').addEventListener('click', function () {
  ComposeWebViewBridge.postMessage(JSON.stringify({
    action: 'buttonClick',
    data: 'some value'
  }));
});

In your Compose code:

@Composable
fun BridgeExample() {
  WebView(
    url = "https://example.com",
    onScriptResult = { message ->
      println("Received from JavaScript: $message")
      // Parse JSON if needed
      val data = Json.decodeFromString<MyData>(message)
      // Handle the message
    }
  )
}

Compose → JavaScript (optional)

If you need to call JavaScript from Compose, pass a WebViewController.

@Composable
fun ComposeToJsExample() {
  val controller = rememberWebViewController()
  val scope = rememberCoroutineScope()

  Column {
    Button(onClick = {
      scope.launch {
        controller.evaluateJavaScript(
          "document.body.style.background = 'tomato';" +
            "window.ComposeWebViewBridge?.postMessage('ack');"
        )
      }
    }) {
      Text("Run JS")
    }

    WebView(
      htmlContent = "<html><body>...</body></html>",
      controller = controller,
      onScriptResult = { msg -> println("JS→Compose: $msg") },
    )
  }
}

Notes:

  • On Desktop/JVM, the script runs best-effort, but return values are not available yet.
  • On Web/WASM, calling JS only works reliably for htmlContent or same-origin content.

📚 Documentation

Detailed documentation is available in the docs directory:

  • Usage & Security Guide: Custom headers, base URL, error handling, URL validation, bridge message security, and Content Security Policy (CSP).
  • Testing Guide: Interop testing limitations, UI testing with test tags, semantics for loading states, and testing bridge communication.
  • Accessibility Guide: Automatic semantics, screen reader announcements, and best practices.
  • Troubleshooting Guide: Solutions for bridge messaging, load errors, blank displays, IDE unresponsiveness, memory leaks, and Desktop session limitations.
  • Performance Considerations: Memory management, caching behavior, and optimization best practices.

🔧 Platform-Specific Notes

Android

  • Uses native Android WebView
  • JavaScript is enabled by default
  • Supports all standard web features

iOS

  • Uses WKWebView
  • Supports modern web standards
  • Bridge communication via WKScriptMessageHandler

Desktop (JVM)

  • Uses Wry (native OS web engine via ComposeNativeWebview)
  • No Chromium bundling — uses the platform's native web engine
  • Bridge communication via JS polyfill (window.javaBridgewindow.kmpJsBridge)

Web (WASM)

  • Uses HTML iframe
  • Cross-origin limitations: Bridge only works for same-origin content
  • For cross-origin URLs, the iframe cannot inject bridge scripts due to browser security

📦 Download Sample App Executables

Download and try the sample app on your platform without building from source:

Platform Download Link
🍏 macOS (Intel) Download
🍎 macOS (Apple Silicon) Download
🪟 Windows Download
🐧 Linux Download
🤖 Android Download
🌐 Web (Wasm) Download
iOS Simulator Download

🚀 How to Run the Sample App

For instructions on how to install and run the pre-built executables or build and run the sample application from source code, please refer to the How to Run the Sample App guide.


License

MIT License © 2025 aryapreetam and contributors. See LICENSE for details.


🙏 Acknowledgments


🤝 Contributing

See CONTRIBUTING.md for detailed contribution guidelines.

Android JVMJVMKotlin/NativeWasm
GitHub stars3
Dependents1
OSS Health
LicenseMIT License
Creation date9 months ago

Last activity24 days ago
Latest release0.0.4 (24 days ago)

CMP WebView - Compose Multiplatform WebView Library

Compose Multiplatform Version Kotlin Version

Release workflow status Maven Central Version MIT License

Android iOS Desktop Web

A WebView library for Compose Multiplatform that enables you to display web content and HTML across Android, iOS, Desktop (JVM), and Web (WASM) platforms with a unified API.

Key Features:

  • 🌐 Load remote URLs (custom headers: Android only)
  • 📄 Display HTML content directly
  • 🔗 JavaScript → Compose messaging (via onScriptResult)
  • 🔁 Compose → JavaScript calls (via WebViewController.evaluateJavaScript)
  • 🔒 Built-in security protections against dangerous URL schemes
  • ♿ Accessibility support with screen reader compatibility
  • 🧪 Testing support with test tags and semantics
  • 🎯 Single API across all platforms (some capabilities are platform-dependent)

✅ Platform capability matrix

Capability Android iOS Desktop (JVM) Web (WASM)
Load remote url (iframe; subject to CSP/X-Frame-Options)
Load htmlContent
Custom request headers (headers)
JS → Compose (onScriptResult) with htmlContent
JS → Compose (onScriptResult) with remote url (bridge injected) (bridge injected) (bridge injected) ⚠️ best-effort (no cross-origin injection)
Compose → JS (WebViewController.evaluateJavaScript) (executes; no return values yet) (same-origin / htmlContent only)
evaluateJavaScript return values (returns Unsupported) (same-origin / htmlContent only)

Notes:

  • On Web/WASM, browsers prevent injecting scripts into cross-origin iframes. For reliable messaging on WASM, prefer htmlContent or same-origin pages you control.
  • On native targets, bridge injection can still be affected by page security policies (e.g., strict CSP). Treat messaging as best-effort for arbitrary third-party pages.
  • On Desktop/JVM, JavaScript execution is best-effort, but evaluateJavaScript currently does not surface return values (it returns WebViewJsResult.Unsupported(...)).

Wasm Demo: Try Live Demo

API Docs: Library API Docs


📦 Installation

Add the dependency to your Compose Multiplatform project:

Option A — Version catalog (recommended)

  1. In gradle/libs.versions.toml:
[versions]
cmpWebview = "0.0.1"

[libraries]
cmp-webview = { module = "io.github.aryapreetam:cmp-webview", version.ref = "cmpWebview" }
  1. In your module's build.gradle.kts:
kotlin {
  sourceSets {
    val commonMain by getting {
      dependencies {
        implementation(libs.cmp.webview)
      }
    }
  }
}

Option B — Direct dependency

kotlin {
  sourceSets {
    val commonMain by getting {
      dependencies {
        implementation("io.github.aryapreetam:cmp-webview:0.0.1")
      }
    }
  }
}

🚀 Quick Start

Load a Remote URL

@Composable
fun MyScreen() {
  WebView(
    url = "https://example.com",
    modifier = Modifier.fillMaxSize(),
    onLoadStarted = { println("Loading started") },
    onLoadFinished = { println("Loading finished") },
    onLoadError = { error -> println("Error: $error") }
  )
}

Display HTML Content

@Composable
fun HtmlScreen() {
  val html = """
        <html>
            <body>
                <h1>Hello from HTML!</h1>
                <p>This is rendered locally.</p>
            </body>
        </html>
    """.trimIndent()

  WebView(
    htmlContent = html,
    modifier = Modifier.fillMaxSize()
  )
}

JavaScript Bridge Communication

JavaScript code in your web content can communicate with your Compose code:

In your HTML/JavaScript:

// Listen for bridge ready event
window.addEventListener('ComposeWebViewBridgeReady', function () {
  // Send message to Compose
  ComposeWebViewBridge.postMessage('Hello from JavaScript!');
});

// Example: Send button click data
document.getElementById('myButton').addEventListener('click', function () {
  ComposeWebViewBridge.postMessage(JSON.stringify({
    action: 'buttonClick',
    data: 'some value'
  }));
});

In your Compose code:

@Composable
fun BridgeExample() {
  WebView(
    url = "https://example.com",
    onScriptResult = { message ->
      println("Received from JavaScript: $message")
      // Parse JSON if needed
      val data = Json.decodeFromString<MyData>(message)
      // Handle the message
    }
  )
}

Compose → JavaScript (optional)

If you need to call JavaScript from Compose, pass a WebViewController.

@Composable
fun ComposeToJsExample() {
  val controller = rememberWebViewController()
  val scope = rememberCoroutineScope()

  Column {
    Button(onClick = {
      scope.launch {
        controller.evaluateJavaScript(
          "document.body.style.background = 'tomato';" +
            "window.ComposeWebViewBridge?.postMessage('ack');"
        )
      }
    }) {
      Text("Run JS")
    }

    WebView(
      htmlContent = "<html><body>...</body></html>",
      controller = controller,
      onScriptResult = { msg -> println("JS→Compose: $msg") },
    )
  }
}

Notes:

  • On Desktop/JVM, the script runs best-effort, but return values are not available yet.
  • On Web/WASM, calling JS only works reliably for htmlContent or same-origin content.

📚 Documentation

Detailed documentation is available in the docs directory:

  • Usage & Security Guide: Custom headers, base URL, error handling, URL validation, bridge message security, and Content Security Policy (CSP).
  • Testing Guide: Interop testing limitations, UI testing with test tags, semantics for loading states, and testing bridge communication.
  • Accessibility Guide: Automatic semantics, screen reader announcements, and best practices.
  • Troubleshooting Guide: Solutions for bridge messaging, load errors, blank displays, IDE unresponsiveness, memory leaks, and Desktop session limitations.
  • Performance Considerations: Memory management, caching behavior, and optimization best practices.

🔧 Platform-Specific Notes

Android

  • Uses native Android WebView
  • JavaScript is enabled by default
  • Supports all standard web features

iOS

  • Uses WKWebView
  • Supports modern web standards
  • Bridge communication via WKScriptMessageHandler

Desktop (JVM)

  • Uses Wry (native OS web engine via ComposeNativeWebview)
  • No Chromium bundling — uses the platform's native web engine
  • Bridge communication via JS polyfill (window.javaBridgewindow.kmpJsBridge)

Web (WASM)

  • Uses HTML iframe
  • Cross-origin limitations: Bridge only works for same-origin content
  • For cross-origin URLs, the iframe cannot inject bridge scripts due to browser security

📦 Download Sample App Executables

Download and try the sample app on your platform without building from source:

Platform Download Link
🍏 macOS (Intel) Download
🍎 macOS (Apple Silicon) Download
🪟 Windows Download
🐧 Linux Download
🤖 Android Download
🌐 Web (Wasm) Download
iOS Simulator Download

🚀 How to Run the Sample App

For instructions on how to install and run the pre-built executables or build and run the sample application from source code, please refer to the How to Run the Sample App guide.


License

MIT License © 2025 aryapreetam and contributors. See LICENSE for details.


🙏 Acknowledgments


🤝 Contributing

See CONTRIBUTING.md for detailed contribution guidelines.