# kmp-webview
> A batteries-included WebView SDK for Kotlin Multiplatform (Android + iOS) — UI scaffolding, a JS bridge, and file/camera support, all out of the box.
[](https://central.sonatype.com/artifact/wang.harlon/kmp-webview)
[](https://kotlinlang.org/docs/multiplatform.html)
[](LICENSE)
English | [中文](./README_ZH.md)
> ⚠️ Early access — the API may still change. See [Releases](https://github.com/HarlonWang/kmp-webview/releases).
## Features
- 🧩 **Drop-in UI** — TopAppBar, bottom navigation, progress bar, and error page out of the box.
- 🔌 **JSBridge** — two-way calls between Kotlin and JS via `window.KmpBridge`.
- 📷 **Device capabilities** — file picker, photo capture, and `getUserMedia`.
- 🐞 **In-app log panel** — console, JS errors, and bridge traffic (debug only).
- 📷 **QR scanner** — optional module that opens scanned URLs (Android).
## Install
Published on Maven Central — make sure `mavenCentral()` is in your repositories.
```kotlin
// KMP — declare once in commonMain; Gradle metadata resolves the platform artifacts
commonMain.dependencies {
implementation("wang.harlon:kmp-webview:latest.version")
}
// Android-only project
implementation("wang.harlon:kmp-webview:latest.version")
```
| Platform | Requirement |
| --- | --- |
| Android | `minSdk 24` |
| iOS | Integrate via KMP (`iosArm64` / `iosSimulatorArm64`); `getUserMedia` needs iOS 14.5+ |
> No standalone XCFramework yet — open an issue if you need one.
## Quick start
```kotlin
@Composable
fun MyScreen(onClose: () -> Unit) {
val state = rememberWebViewState("https://example.com")
WebViewScreen(state = state, onCloseRequest = onClose)
}
```
Behavior is tuned through [`WebViewConfig`](library/src/commonMain/kotlin/wang/harlon/webview/core/WebViewConfig.kt): UA strategy, `allow*` toggles, a custom error-page slot, and bottom-bar / progress-bar visibility.
## JSBridge
Register native handlers in Kotlin and call them from JS:
```kotlin
val state = rememberWebViewState("https://example.com")
state.jsBridge.registerHandler("getToken") { params -> """{"token":"${authRepo.fetchToken()}"}""" }
state.jsBridge.emit("auth.changed", """{"loggedIn":true}""")
```
```js
const token = await KmpBridge.call('getToken', { scope: 'user' });
const off = KmpBridge.on('auth.changed', payload => { /* ... */ });
```
Handlers are `suspend (String?) -> String?`; throw `JsBridgeException(code, message)` to propagate an error code. The JS entry point (`window.KmpBridge`) can be renamed via `rememberWebViewState(bridgeNamespace = "...")`.
See the [JSBridge design](docs/superpowers/specs/2026-05-25-jsbridge-design.md), [custom naming](docs/superpowers/specs/2026-05-25-jsbridge-custom-naming-design.md), and the [frontend guide](docs/frontend-bridge.md).
## Setup
The SDK declares only `INTERNET`. Declare camera/microphone permissions yourself, only when you use those features.
**Android** — `FileProvider` is built in (no setup needed). For `http://` URLs, enable `usesCleartextTraffic` or a network-security-config.
```xml
```
**iOS** — add the usage-description keys you need to `Info.plist`: `NSCameraUsageDescription`, `NSMicrophoneUsageDescription`, `NSPhotoLibraryUsageDescription`. `getUserMedia` is iOS 14.5+ only (no polyfill).
## QR scanner (optional, Android)
`wang.harlon:kmp-webview-scanner` is a separate module — the core library does not depend on it.
```kotlin
implementation("wang.harlon:kmp-webview-scanner:latest.version")
```
```kotlin
QrScannerScreen(
onResult = { url -> state.loadUrl(url); showScanner = false }, // only valid http/https URLs
onCancel = { showScanner = false },
)
```
Built on CameraX + ZXing (offline, no Google Play Services). Only `http`/`https` URLs with a host are accepted; the module declares `CAMERA` itself and requests it on entry. See the [design doc](docs/superpowers/specs/2026-05-29-qr-scanner-design.md).
## Log panel (debug)
Set `WebViewConfig(enableLogPanel = true)` to inject an on-device, filterable log panel that captures JS `console.*`, uncaught JS errors, WebView load/resource errors, and JSBridge traffic.
> **Debug only — never enable in production.** Bridge payloads enter the panel verbatim and may carry tokens or other sensitive data. The SDK does not redact them. When `false` (the default), nothing is injected. See the [design doc](docs/superpowers/specs/2026-05-28-webview-log-panel-design.md).
## License
[MIT](LICENSE)。已发版本的 POM 元数据有误,以 LICENSE 为准。