Advanced Features¶
This guide covers advanced capabilities of compose-webview including WebView Configuration, State Management, JavaScript Debugging, File Uploads, Downloads, and Custom Views (Fullscreen Video).
Platform Support Matrix¶
| Feature | Android | iOS | Desktop | Web (JS) | Web (WASM) |
|---|---|---|---|---|---|
| WebViewSettings Configuration | ✅ | ✅ | ⚠️ | ❌ | ❌ |
| Network Interception | ✅ | ✅ | ❌ | ❌ | ❌ |
| Dark Mode Support | ✅ | ✅ | ❌ | ❌ | ❌ |
| Console Message Debugging | ✅ | ✅ | ❌ | ❌ | ❌ |
| Scroll Position Tracking | ✅ | ✅ | ❌ | ⚠️ | ⚠️ |
| Loading State with Progress | ✅ | ✅ | ⚠️ | ⚠️ | ⚠️ |
| Find on Page | ✅ | ✅ | ❌ | ❌ | ❌ |
| Typed Error Handling | ✅ | ✅ | ⚠️ | ⚠️ | ⚠️ |
| File Uploads | ✅ | ✅ | ❌ | ❌ | ❌ |
| Downloads | ✅ | ⚠️ | ❌ | ❌ | ❌ |
| Custom View (Fullscreen) | ✅ | ❌ | ❌ | ❌ | ❌ |
| JS Dialogs (Alert/Confirm/Prompt) | ✅ | ✅ | ❌ | ❌ | ❌ |
Legend: ✅ Full Support | ⚠️ Partial/Limited | ❌ Not Supported
WebView Configuration (WebViewSettings)¶
Configure WebView behavior with a unified API across platforms.
Basic Usage¶
val settings = WebViewSettings(
userAgent = "MyApp/1.0",
javaScriptEnabled = true,
domStorageEnabled = true,
cacheMode = CacheMode.CACHE_ELSE_NETWORK,
supportZoom = true,
mediaPlaybackRequiresUserAction = false
)
ComposeWebView(
state = webViewState,
settings = settings
)
Available Settings¶
| Setting | Android | iOS | Desktop | Web (JS) | Web (WASM) | Notes |
|---|---|---|---|---|---|---|
interceptedSchemes |
✅ | ✅ | ❌ | ❌ | ❌ | Schemes to intercept (e.g., "http", "https", "myapp") |
darkMode |
✅ | ✅ | ❌ | ❌ | ❌ | Theme mode (AUTO, LIGHT, DARK) |
userAgent |
✅ | ✅ | ✅ | ❌ | ❌ | Custom user agent string |
javaScriptEnabled |
✅ | ✅* | ✅ | ❌ | ❌ | *iOS: Always enabled |
domStorageEnabled |
✅ | ✅ | ⚠️ | ❌ | ❌ | localStorage/sessionStorage |
cacheMode |
✅ | ⚠️ | ⚠️ | ❌ | ❌ | Cache behavior control |
supportZoom |
✅ | ⚠️** | ✅ | ❌ | ❌ | **iOS: Pinch-to-zoom only |
mediaPlaybackRequiresUserAction |
✅ | ✅ | ⚠️ | ❌ | ❌ | Autoplay control |
Platform-Specific Behavior
- iOS: JavaScript is always enabled and cannot be disabled. The
javaScriptEnabledsetting is ignored. - iOS: Programmatic zoom (
zoomIn()/zoomOut()) is not supported. Only user pinch-to-zoom gestures work. - Web: Settings are browser-controlled and cannot be modified from the iframe.
Cache Modes¶
enum class CacheMode {
DEFAULT, // Use cache when available
CACHE_ELSE_NETWORK, // Prefer cache, fallback to network
NO_CACHE, // Always load from network
CACHE_ONLY // Only use cache, don't fetch
}
Console Message Debugging¶
Capture and debug JavaScript console messages from your WebView.
Console Message Platform Support¶
| Platform | Status | Implementation |
|---|---|---|
| Android | ✅ Full | WebChromeClient.onConsoleMessage |
| iOS | ✅ Full | Custom message handler |
| Desktop/Web | ❌ Not supported | - |
Console Message Usage¶
@Composable
fun DebuggableWebView() {
val state = rememberSaveableWebViewState(url = "https://example.com")
// Create handling chrome client
val chromeClient = rememberWebChromeClient {
onConsoleMessage { webView, message ->
when (message.level) {
ConsoleMessageLevel.ERROR -> {
Log.e("WebView", "[${message.sourceId}:${message.lineNumber}] ${message.message}")
}
ConsoleMessageLevel.WARNING -> {
Log.w("WebView", message.message)
}
ConsoleMessageLevel.LOG -> {
Log.d("WebView", message.message)
}
else -> {
Log.v("WebView", message.message)
}
}
false // Return true to suppress default logging
}
}
ComposeWebView(
state = state,
chromeClient = chromeClient
)
}
ConsoleMessage Data Class¶
data class ConsoleMessage(
val message: String, // Console message text
val sourceId: String = "", // Source file URL
val lineNumber: Int = 0, // Line number in source
val level: ConsoleMessageLevel // Severity level
)
enum class ConsoleMessageLevel {
LOG, DEBUG, WARNING, ERROR, TIP
}
Scroll Position Tracking¶
Track the WebView's scroll position in real-time.
Scroll Position Platform Support¶
| Platform | Support | Implementation | Update Frequency |
|---|---|---|---|
| Android | ✅ Full | setOnScrollChangeListener |
Real-time |
| iOS | ✅ Full | contentOffset polling |
100ms intervals |
| Desktop | ❌ Not supported | CEF limitations | - |
| Web | ⚠️ Limited | onscroll event |
CORS restricted (same-origin only) |
Scroll Position Usage¶
val webViewState = rememberWebViewState(url = "https://example.com")
// Access scroll position from state
LaunchedEffect(webViewState.scrollPosition) {
val (x, y) = webViewState.scrollPosition
println("Scrolled to: x=$x, y=$y")
}
ComposeWebView(state = webViewState)
ScrollPosition Data Class¶
@Immutable
data class ScrollPosition(
val x: Int = 0, // Horizontal scroll position in pixels
val y: Int = 0 // Vertical scroll position in pixels
)
Platform Limitations
- iOS: Uses polling (100ms intervals) instead of real-time updates due to Kotlin/Native KVO complexity.
- Web: Only works for same-origin iframes. Cross-origin iframes will always report (0, 0) due to CORS restrictions.
- Desktop: Not supported due to KCEF API limitations.
File Uploads¶
Uploading files (e.g., via <input type="file">) is often a hassle to implement in Android WebViews because it requires handling WebChromeClient.onShowFileChooser.
File Upload Platform Support¶
| Platform | Status | Notes |
|---|---|---|
| Android | ✅ Automatic | Uses onShowFileChooser internally |
| iOS | ✅ Native | WKWebView handles file uploads by default |
| Desktop/Web | ❌ Not supported |
How it works (Android)¶
The library internally uses rememberLauncherForActivityResult to launch the Android File Picker intent when the WebView requests a file. When the user selects a file, the result is automatically passed back to the WebView.
No Extra Code
You do NOT need to implement onShowFileChooser or handle Activity results manually. It works out-of-the-box.
How it works (iOS)¶
iOS's WKWebView natively supports file uploads without any additional configuration. The system automatically presents a file picker when a web page requests file input.
iOS 18.4+ Custom Implementation
Starting from iOS 18.4, you can optionally implement WKUIDelegate.runOpenPanelWith for custom file picker UI. However, this is not required for basic file upload functionality.
Permissions¶
Standard Android file picking usually does not require runtime permissions on modern Android versions (API 21+). However, if your web page requests camera access (e.g., <input type="file" capture>), ensure you have declared and requested CAMERA permission in your app.
Downloads¶
By default, WebView does not handle file downloads. You need to provide a callback to intercept download requests.
Handling Downloads¶
Use the onDownloadStart parameter to receive download events.
ComposeWebView(
url = "https://example.com",
onDownloadStart = { url, userAgent, contentDisposition, mimeType, contentLength ->
// Trigger download
downloadFile(url, contentDisposition, mimeType)
}
)
Example Implementation¶
You can use Android's DownloadManager to handle the actual download.
fun downloadFile(context: Context, url: String, contentDisposition: String?, mimeType: String?) {
val request = DownloadManager.Request(Uri.parse(url))
request.setMimeType(mimeType)
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
// Guess filename
val filename = URLUtil.guessFileName(url, contentDisposition, mimeType)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename)
val dm = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
dm.enqueue(request)
}
Custom Views (Fullscreen Video)¶
To support fullscreen video (e.g., YouTube's fullscreen button), you need to handle "Custom Views".
iOS behavior
On iOS the actual fullscreen video is presented by WKWebView using the system player. When it appears, customViewState becomes non-null so you can hide toolbars or overlays, but the placeholder view does not render the video itself.
1. Provide Custom View Content¶
Use the customViewContent parameter. This lambda is only called when a video requests fullscreen.
ComposeWebView(
// ...
customViewContent = { customViewState ->
// customViewState.customView is the video implementation provided by WebView
if (customViewState.customView != null) {
AndroidView(
factory = { customViewState.customView!! },
modifier = Modifier
.fillMaxSize()
.background(Color.Black) // Background for fullscreen
)
}
}
)
2. Android Manifest Configuration¶
For fullscreen video to work smoothly (and to allow orientation changes), your Activity in AndroidManifest.xml should handle configuration changes manually.
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden|smallestScreenSize|screenLayout"
android:hardwareAccelerated="true"> <!-- Required for video -->
Request Interception¶
Intercept network requests and provide custom local responses.
1. Configuration (iOS specific)¶
On iOS, you must explicitly register the schemes you want to intercept in WebViewSettings. This is required because iOS doesn't allow intercepting standard schemes (http/https) without a custom WKURLSchemeHandler.
val settings = WebViewSettings(
interceptedSchemes = setOf("https", "myapp") // Schemes to intercept
)
2. Implementation¶
Use the shouldInterceptRequest handler in ComposeWebViewClient.
val client = rememberWebViewClient {
shouldInterceptRequest { webView, request ->
if (request?.url?.contains("intercept-test") == true) {
createPlatformWebResourceResponse(
mimeType = "text/html",
encoding = "UTF-8",
data = "<html><body><h1>Intercepted!</h1></body></html>".encodeToByteArray(),
)
} else {
null // Let the WebView handle original request
}
}
}
Dark Mode Support¶
ComposeWebView supports automatic dark mode synchronization with the system theme or manual control.
Dark Mode Usage¶
val settings = WebViewSettings(
darkMode = DarkMode.AUTO // AUTO (System), LIGHT (Always Light), DARK (Always Dark)
)
Platform Behavior¶
- Android: Uses
WebSettingsCompat.setForceDarkor algorithmic darkening depending on API level. - iOS: Uses
overrideUserInterfaceStyleand informs the system theme to the WebView.
Cookie Management¶
The library provides a global PlatformCookieManager to manage browser cookies across all platforms.
Cookie Usage¶
val cookieManager = PlatformCookieManager.instance
// Remove all cookies for a specific URL
cookieManager.removeCookies("https://google.com")
// Get cookies for a URL
val cookies = cookieManager.getCookies("https://google.com")
Find on Page¶
Search for text within the current page and get real-time feedback on match results.
Find on Page Usage¶
val controller = rememberWebViewController()
var matchCount by remember { mutableStateOf(0) }
ComposeWebView(
state = state,
controller = controller,
onFindResultReceived = { activeIndex, count, isDone ->
matchCount = count
}
)
// Trigger search
controller.findAllAsync("Compose")
controller.findNext(true) // Next match
Custom Context Menu (Android)¶
Override the default Android action mode (context menu) with custom callbacks.