
Cross-platform WebView component exposing WebViewState and navigator APIs; backed by native OS webviews via Rust+UniFFI (Wry), with JS-native bridge, cookie API, request interceptor.
ComposeNativeWebView is a Compose Multiplatform WebView whose API design and mobile implementations (Android & iOS) are intentionally derived almost verbatim from KevinnZou/compose-webview-multiplatform.
This project exists first and foremost to bring that same API to Desktop, backed by native OS webviews instead of a bundled Chromium runtime.
io.github.kdroidfilter.webview.*
🟢 Reused on purpose
WebViewState, WebViewNavigator, settings, callbacks, mental model)android.webkit.WebView)WKWebView)👉 If you already know compose-webview-multiplatform, you already know how to use this.
🆕 What ComposeNativeWebView adds
✅ Android: android.webkit.WebView
✅ iOS: WKWebView
✅ Desktop: Wry (Rust) via UniFFI
Desktop engines:
@Composable
fun App() {
val state = rememberWebViewState("https://example.com")
WebView(state, Modifier.fillMaxSize())
}That’s it.
dependencies {
implementation("io.github.kdroidfilter:composewebview:<version>")
}Same artifact for Android, iOS, Desktop.
Wry uses native access via JNA.
compose.desktop {
application {
jvmArgs += "--enable-native-access=ALL-UNNAMED"
}
}Run the feature showcase first:
./gradlew :demo:run
./gradlew :demo-android:installDebug
iosApp/iosApp.xcodeproj in Xcode and RunResponsive UI:
loadUrl(url, headers)loadHtml(html)loadHtmlFile(fileName, readType)navigateBack(), navigateForward()
reload(), stopLoading()
canGoBack, canGoForward
isLoadingloadingStatelastLoadedUrlpageTitleUnified cookie API:
state.cookieManager.setCookie(...)
state.cookieManager.getCookies(url)
state.cookieManager.removeCookies(url)
state.cookieManager.removeAllCookies()navigator.evaluateJavaScript("document.title = 'Hello'")window.kmpJsBridge.callNative("echo", {...}, callback)Intercept navigator-initiated navigations only:
override fun onInterceptUrlRequest(
request: WebRequest,
navigator: WebViewNavigator
): WebRequestInterceptResultUseful for:
val state = rememberWebViewState(
url = "https://example.com"
) {
customUserAgentString = "MyApp/1.0"
}Supports:
val navigator = rememberWebViewNavigator()
WebView(state, navigator)Commands:
loadUrlloadHtmlloadHtmlFileevaluateJavaScriptstate.webSettings.customUserAgentString = "MyApp/1.2.3"Desktop note:
👉 Set it early.
state.webSettings.logSeverity = KLogSeverity.DebugWebView(
state,
navigator,
onCreated = { native ->
println(native.getCurrentUrl())
}
)Useful for debugging or platform-specific hooks.
wrywebview/ → Rust core + UniFFI bindingswrywebview-compose/ → Compose APIdemo-shared/ → shared demo UIdemo/, demo-android/, iosApp/ → platform launchersComposeNativeWebView is a Compose Multiplatform WebView whose API design and mobile implementations (Android & iOS) are intentionally derived almost verbatim from KevinnZou/compose-webview-multiplatform.
This project exists first and foremost to bring that same API to Desktop, backed by native OS webviews instead of a bundled Chromium runtime.
io.github.kdroidfilter.webview.*
🟢 Reused on purpose
WebViewState, WebViewNavigator, settings, callbacks, mental model)android.webkit.WebView)WKWebView)👉 If you already know compose-webview-multiplatform, you already know how to use this.
🆕 What ComposeNativeWebView adds
✅ Android: android.webkit.WebView
✅ iOS: WKWebView
✅ Desktop: Wry (Rust) via UniFFI
Desktop engines:
@Composable
fun App() {
val state = rememberWebViewState("https://example.com")
WebView(state, Modifier.fillMaxSize())
}That’s it.
dependencies {
implementation("io.github.kdroidfilter:composewebview:<version>")
}Same artifact for Android, iOS, Desktop.
Wry uses native access via JNA.
compose.desktop {
application {
jvmArgs += "--enable-native-access=ALL-UNNAMED"
}
}Run the feature showcase first:
./gradlew :demo:run
./gradlew :demo-android:installDebug
iosApp/iosApp.xcodeproj in Xcode and RunResponsive UI:
loadUrl(url, headers)loadHtml(html)loadHtmlFile(fileName, readType)navigateBack(), navigateForward()
reload(), stopLoading()
canGoBack, canGoForward
isLoadingloadingStatelastLoadedUrlpageTitleUnified cookie API:
state.cookieManager.setCookie(...)
state.cookieManager.getCookies(url)
state.cookieManager.removeCookies(url)
state.cookieManager.removeAllCookies()navigator.evaluateJavaScript("document.title = 'Hello'")window.kmpJsBridge.callNative("echo", {...}, callback)Intercept navigator-initiated navigations only:
override fun onInterceptUrlRequest(
request: WebRequest,
navigator: WebViewNavigator
): WebRequestInterceptResultUseful for:
val state = rememberWebViewState(
url = "https://example.com"
) {
customUserAgentString = "MyApp/1.0"
}Supports:
val navigator = rememberWebViewNavigator()
WebView(state, navigator)Commands:
loadUrlloadHtmlloadHtmlFileevaluateJavaScriptstate.webSettings.customUserAgentString = "MyApp/1.2.3"Desktop note:
👉 Set it early.
state.webSettings.logSeverity = KLogSeverity.DebugWebView(
state,
navigator,
onCreated = { native ->
println(native.getCurrentUrl())
}
)Useful for debugging or platform-specific hooks.
wrywebview/ → Rust core + UniFFI bindingswrywebview-compose/ → Compose APIdemo-shared/ → shared demo UIdemo/, demo-android/, iosApp/ → platform launchers