Skip to content

Latest commit

 

History

History
296 lines (195 loc) · 5.39 KB

File metadata and controls

296 lines (195 loc) · 5.39 KB

ComposeNativeWebView 🌐

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.*

What is reused vs what is new

🟢 Reused on purpose

  • API surface (WebViewState, WebViewNavigator, settings, callbacks, mental model)
  • Android implementation (android.webkit.WebView)
  • iOS implementation (WKWebView)
  • Overall behavior and semantics

👉 If you already know compose-webview-multiplatform, you already know how to use this.

🆕 What ComposeNativeWebView adds

  • Desktop support with native engines
  • A Rust + UniFFI (Wry) backend instead of KCEF / embedded Chromium
  • A tiny desktop footprint with system-provided webviews
  • Handling of the WasmJs target via IFrame usage

Platform backends

Android: android.webkit.WebViewiOS: WKWebViewWasmJs: org.w3c.dom.HTMLIFrameElementDesktop: Wry (Rust) via UniFFI

Desktop engines:

  • Windows: WebView2
  • macOS: WKWebView
  • Linux: WebKitGTK

Quick start 🚀

@Composable
fun App() {
  val state = rememberWebViewState("https://example.com")
  WebView(state, Modifier.fillMaxSize())
}

That’s it.


Installation 🧩

Dependency (all platforms)

dependencies {
  implementation("io.github.kdroidfilter:composewebview:<version>")
}

Same artifact for Android, iOS, Desktop and WasmJs.


Desktop only: enable native access ⚠️

Wry uses native access via JNA.

compose.desktop {
  application {
    jvmArgs += "--enable-native-access=ALL-UNNAMED"
  }
}

Demo app 🎮

Run the feature showcase first:

  • Desktop: ./gradlew :demo:run
  • Android: ./gradlew :demo-android:installDebug
  • WasmJs: ./gradlew :demo-wasmJs:wasmJsBrowserDevelopmentRun
  • iOS: open iosApp/iosApp.xcodeproj in Xcode and Run

Responsive UI:

  • large screens → side Tools panel
  • phones → bottom sheet

Core features ✨

Content loading

  • loadUrl(url, headers)
  • loadHtml(html)
  • loadHtmlFile(fileName, readType)

Navigation

  • navigateBack(), navigateForward()
  • reload(), stopLoading()
  • canGoBack, canGoForward

Observable state

  • isLoading
  • loadingState
  • lastLoadedUrl
  • pageTitle

Cookies 🍪

Unified cookie API:

state.cookieManager.setCookie(...)
state.cookieManager.getCookies(url)
state.cookieManager.removeCookies(url)
state.cookieManager.removeAllCookies()

JavaScript

navigator.evaluateJavaScript("document.title = 'Hello'")

JS ↔ Kotlin bridge 🌉

  • injected automatically after page load
  • callback-based
  • works on all platforms
window.kmpJsBridge.callNative("echo", {...}, callback)

RequestInterceptor 🚦

Intercept navigator-initiated navigations only:

override fun onInterceptUrlRequest(
  request: WebRequest,
  navigator: WebViewNavigator
): WebRequestInterceptResult

Useful for:

  • blocking URLs
  • app-driven routing
  • security rules

WebViewState & Navigator 📘

State creation

val state = rememberWebViewState(
  url = "https://example.com"
) {
  customUserAgentString = "MyApp/1.0"
}

Supports:

  • URL
  • inline HTML
  • resource files

Navigator

val navigator = rememberWebViewNavigator()
WebView(state, navigator)

Commands:

  • loadUrl
  • loadHtml
  • loadHtmlFile
  • evaluateJavaScript

Settings ⚙️

Custom User-Agent

state.webSettings.customUserAgentString = "MyApp/1.2.3"

Desktop note:

  • applied at creation time
  • changing it recreates the WebView (debounced)
  • JS context/history may be lost

👉 Set it early.


Logging

state.webSettings.logSeverity = KLogSeverity.Debug

Desktop advanced 🖥️

Access native WebView handle

WebView(
  state,
  navigator,
  onCreated = { native ->
    println(native.getCurrentUrl())
  }
)

Useful for debugging or platform-specific hooks.


Project structure 🗂️

  • wrywebview/ → Rust core + UniFFI bindings
  • wrywebview-compose/ → Compose API
  • demo-shared/ → shared demo UI
  • demo/, demo-android/, demo-wasmJs/, iosApp/ → platform launchers

Limitations ⚠️

  • RequestInterceptor does not intercept sub-resources

Desktop

  • Desktop UA change recreates the WebView

WasmJs

  • Navigation back and forward is not available in the IFrame.
  • The IFrame will work only if the target website has appropriately configured its CORS.
  • JS can be executed only on the same origin.
  • Cookies can be set only for the parent destination (when the destination of the iframe is the same as the parent destination - cookies can be set. Otherwise, they will be ignored (there is a hack for it, but it is not a clean solution then https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#security)

Credits 🙏

  • API inspiration: KevinnZou/compose-webview-multiplatform
  • Wry (Tauri ecosystem)
  • UniFFI (Mozilla)