Shared UI entry point
Android, iOS, and JVM desktop use the same WebView Composable.
Many apps need to embed web-delivered content in shared UI: a help center, campaign page, login callback, or a module published by another team. The screen does not need to become a full browser. Compose owns the app chrome, back entry point, and status messages; WebView renders the page in the remaining space.
wvbridge models this with two objects: controller manages state and operations for one native WebView, while WebView renders it in the current Compose layout. Both can be used from commonMain.
Shared UI entry point
Android, iOS, and JVM desktop use the same WebView Composable.
Observable page state
The controller exposes the current URL and loading state for titles and progress indicators.
Continued control of one instance
Navigation, interception, and JavaScript capabilities all continue from the controller.
Complete installation first. This screen keeps a Compose title bar and gives the remaining space to the page. Create the controller inside a Composable with rememberWebViewController(); do not construct a native WebView on every recomposition.
WebView and give it usable layout dimensions.import androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.weightimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.ui.Modifierimport top.kagg886.wvbridge.WebViewimport top.kagg886.wvbridge.rememberWebViewController
@Composablefun HelpCenterScreen() { val controller = rememberWebViewController( url = "https://example.com/help", )
Column(Modifier.fillMaxSize()) { Text( text = "Help center", modifier = Modifier.fillMaxWidth(), )
WebView( controller = controller, modifier = Modifier.weight(1f), ) }}On first composition the controller creates the platform view; once ready, the library loads the initial URL. You do not need to call loadUrl() to start the first navigation.
The controller is a source of Compose state and a place for button actions. This is an app content page, not a complete browser:
import androidx.compose.material3.Buttonimport androidx.compose.material3.CircularProgressIndicatorimport androidx.compose.material3.Textimport top.kagg886.wvbridge.LoadingState
if (controller.loadingState is LoadingState.Loading) { CircularProgressIndicator()}
Button( enabled = controller.navigator.canGoBack, onClick = { controller.navigator.goBack() },) { Text("Back in page")}url stores the most recently attempted top-level address, including failed navigations. loadingState describes native-view readiness and loading. See the controller guide for its full lifecycle and the navigator, bridge, and interceptor responsibilities.
| Target | Native host | Layout note |
|---|---|---|
| Android | System android.webkit.WebView |
Integrated through Compose AndroidView; allocate an explicit size. |
| iOS | WKWebView |
Integrated through Compose UIKit interop; allocate an explicit size. |
| JVM desktop | On-screen native view hosted by SwingPanel |
Not an off-screen browser canvas; do not rely on Compose/Swing content covering the page. |
JVM also requires a platform-* native runtime at the same version as core, and currently supports Windows x64, Linux x64, and macOS arm64. See the JVM runtime installation notes.