Skip to content

ComposeNativeWebView is a lightweight Compose Multiplatform WebView using native OS web engines via Wry (Rust) + UniFFI. It provides a familiar API without bundling Chromium, resulting in smaller binaries and faster startup across Android, iOS, and Desktop.

License

Notifications You must be signed in to change notification settings

kdroidFilter/ComposeNativeWebview

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

49 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

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

Platform backends

โœ… Android: android.webkit.WebView โœ… iOS: WKWebView โœ… Desktop: 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.


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
  • 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/, iosApp/ โ†’ platform launchers

Limitations โš ๏ธ

  • RequestInterceptor does not intercept sub-resources
  • Desktop UA change recreates the WebView

Credits ๐Ÿ™

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

About

ComposeNativeWebView is a lightweight Compose Multiplatform WebView using native OS web engines via Wry (Rust) + UniFFI. It provides a familiar API without bundling Chromium, resulting in smaller binaries and faster startup across Android, iOS, and Desktop.

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •