
Native WebView integration leveraging system web rendering via JNA, offering true native performance, no bundled browser engines, bidirectional JS interop, navigation controls, and state management.
A powerful native WebView integration for Compose Multiplatform that provides seamless web content rendering across Android, iOS, Desktop (Windows/macOS), and JVM platforms. Built with native platform APIs for superior performance, authentic user experience, and zero external dependencies.
True Native Integration β Leverages each platform's native WebView components: Android WebView, iOS WKWebView, Windows WebView2 (Chromium), and macOS WKWebView for authentic platform behavior.
Universal Cross-Platform Support β Single API that works seamlessly across Android, iOS, Windows, macOS, and Linux (community-supported).
Zero Bundled Dependencies β No embedded browsers or heavy dependencies. Uses the web rendering technology already present on each platform.
Production-Ready Performance β Battle-tested in real-world applications with native-level performance and memory efficiency.
Compose-First Design β Idiomatic Kotlin Multiplatform API built specifically for Compose developers with reactive state management.
Enterprise-Grade Security β Inherits security features and automatic updates from each platform's native WebView implementation.
Add the dependency to your build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.saralapps:composemultiplatformwebview:0.1.4")
}
}
}Android:
iOS:
Windows (x64):
macOS:
Linux:
import com.saralapps.composemultiplatformwebview.PlatformWebView
@Composable
fun App() {
PlatformWebView(
url = "https://kotlinlang.org",
modifier = Modifier.fillMaxSize()
)
}@Composable
fun WebViewWithState() {
val webViewState = rememberPlatformWebViewState(
url = "https://github.com",
javaScriptEnabled = true,
allowsFileAccess = true
)
PlatformWebView(
state = webViewState,
modifier = Modifier.fillMaxSize(),
onUrlChanged = { newUrl ->
println("Navigated to: $newUrl")
}
)
}@Composable
fun InteractiveBrowser() {
var currentUrl by remember { mutableStateOf("https://example.com") }
var isLoading by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize()) {
// Navigation bar
Row(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
TextField(
value = currentUrl,
onValueChange = { currentUrl = it },
modifier = Modifier.weight(1f),
placeholder = { Text("Enter URL") },
singleLine = true
)
Button(
onClick = { /* Trigger navigation */ },
enabled = !isLoading
) {
Text("Go")
}
}
// Loading indicator
if (isLoading) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth()
)
}
// WebView
PlatformWebView(
url = currentUrl,
modifier = Modifier.weight(1f),
javaScriptEnabled = true,
onUrlChanged = { newUrl ->
currentUrl = newUrl
isLoading = false
},
onNavigating = { url ->
isLoading = true
true // Allow navigation
}
)
}
}@Composable
fun WebViewWithFallback() {
PlatformWebView(
url = "https://example.com",
modifier = Modifier.fillMaxSize(),
onUnavailable = { availability ->
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
when (availability) {
is WebViewAvailability.NotInstalled -> {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("WebView not available on this device")
Button(onClick = { /* Handle installation */ }) {
Text("Install WebView2")
}
}
}
is WebViewAvailability.Error -> {
Text("Error: ${availability.message}")
}
else -> {
Text("WebView unavailable")
}
}
}
}
)
}Manages the internal state and configuration of the WebView:
val webViewState = rememberPlatformWebViewState(
url = "https://example.com",
javaScriptEnabled = true,
allowsFileAccess = false,
onNavigating = { url ->
// Return true to allow, false to block navigation
url.startsWith("https://")
}
)Parameters:
url: String? - Initial URL to load (optional)javaScriptEnabled: Boolean - Enable/disable JavaScript execution (default: true)allowsFileAccess: Boolean - Allow/deny local file access (default: true)onNavigating: ((String) -> Boolean)? - Navigation interception callbackTwo variants available for different use cases:
Best for complex scenarios requiring state management:
@Composable
fun PlatformWebView(
state: PlatformWebViewState,
modifier: Modifier = Modifier,
placeholderColor: Color = Color.White,
onUrlChanged: ((String) -> Unit)? = null,
onCreated: (() -> Unit)? = null,
onDisposed: (() -> Unit)? = null,
onUnavailable: @Composable ((WebViewAvailability) -> Unit)? = null
)Perfect for straightforward WebView integration:
@Composable
fun PlatformWebView(
url: String,
modifier: Modifier = Modifier,
javaScriptEnabled: Boolean = true,
allowsFileAccess: Boolean = true,
placeholderColor: Color = Color.White,
onUrlChanged: ((String) -> Unit)? = null,
onNavigating: ((String) -> Boolean)? = null,
onCreated: (() -> Unit)? = null,
onDisposed: (() -> Unit)? = null,
onUnavailable: @Composable ((WebViewAvailability) -> Unit)? = null
)| Parameter | Type | Description |
|---|---|---|
modifier |
Modifier |
Compose modifier for layout and styling |
placeholderColor |
Color |
Background color during WebView initialization |
onUrlChanged |
((String) -> Unit)? |
Callback triggered when URL changes |
onNavigating |
((String) -> Boolean)? |
Pre-navigation callback; return false to block |
onCreated |
(() -> Unit)? |
Callback when WebView is successfully created |
onDisposed |
(() -> Unit)? |
Callback when WebView is disposed |
onUnavailable |
@Composable ((WebViewAvailability) -> Unit)? |
Composable shown when WebView unavailable |
sealed class WebViewAvailability {
object Available : WebViewAvailability()
object NotInstalled : WebViewAvailability()
data class Error(val message: String) : WebViewAvailability()
}@Composable
fun SecureWebView(url: String) {
val webViewState = rememberPlatformWebViewState(
url = url,
javaScriptEnabled = false, // Disable for untrusted content
allowsFileAccess = false, // Prevent file access
onNavigating = { navigationUrl ->
// Whitelist allowed domains
val allowedDomains = listOf("example.com", "api.example.com")
val uri = URI(navigationUrl)
allowedDomains.any { uri.host?.endsWith(it) == true }
}
)
PlatformWebView(
state = webViewState,
modifier = Modifier.fillMaxSize()
)
}@Composable
fun DynamicContentViewer() {
var selectedContent by remember {
mutableStateOf("https://kotlinlang.org")
}
val contentOptions = mapOf(
"Kotlin" to "https://kotlinlang.org",
"Compose" to "https://www.jetbrains.com/lp/compose-multiplatform/",
"GitHub" to "https://github.com"
)
Column(modifier = Modifier.fillMaxSize()) {
// Content selector
Row(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
contentOptions.forEach { (label, url) ->
Button(
onClick = { selectedContent = url },
colors = ButtonDefaults.buttonColors(
containerColor = if (selectedContent == url)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.secondary
)
) {
Text(label)
}
}
}
// WebView with dynamic content
PlatformWebView(
url = selectedContent,
modifier = Modifier.weight(1f),
javaScriptEnabled = true
)
}
}@Composable
fun TabbedBrowser() {
var tabs by remember {
mutableStateOf(listOf(
"https://kotlinlang.org",
"https://github.com"
))
}
var selectedTab by remember { mutableStateOf(0) }
Column(modifier = Modifier.fillMaxSize()) {
// Tab row
ScrollableTabRow(selectedTabIndex = selectedTab) {
tabs.forEachIndexed { index, url ->
Tab(
selected = selectedTab == index,
onClick = { selectedTab = index },
text = {
Text(
url.substringAfter("://")
.substringBefore("/")
.take(20)
)
}
)
}
}
// Active tab content
PlatformWebView(
url = tabs[selectedTab],
modifier = Modifier.weight(1f),
javaScriptEnabled = true,
onUrlChanged = { newUrl ->
tabs = tabs.toMutableList().apply {
set(selectedTab, newUrl)
}
}
)
}
}@Composable
fun WebViewWithLifecycle() {
var webViewActive by remember { mutableStateOf(false) }
var pageTitle by remember { mutableStateOf("") }
PlatformWebView(
url = "https://example.com",
modifier = Modifier.fillMaxSize(),
onCreated = {
webViewActive = true
println("WebView initialized successfully")
},
onUrlChanged = { url ->
// Update page title or handle navigation
pageTitle = url.substringAfter("://").substringBefore("/")
},
onDisposed = {
webViewActive = false
println("WebView resources released")
}
)
}Control JavaScript execution based on content trust level:
// Untrusted external content
PlatformWebView(
url = "https://untrusted-site.com",
javaScriptEnabled = false, // Disable JavaScript
allowsFileAccess = false,
modifier = Modifier.fillMaxSize()
)
// Trusted application content
PlatformWebView(
url = "https://your-app.com",
javaScriptEnabled = true,
modifier = Modifier.fillMaxSize()
)// Web content (recommended: deny file access)
val webState = rememberPlatformWebViewState(
url = "https://example.com",
allowsFileAccess = false
)
// Local HTML content (required: allow file access)
val localState = rememberPlatformWebViewState(
url = "file:///android_asset/index.html",
allowsFileAccess = true
)val secureWebViewState = rememberPlatformWebViewState(
url = "https://myapp.com",
onNavigating = { url ->
when {
// Block non-HTTPS
!url.startsWith("https://") -> false
// Block tracking and ads
url.contains("analytics") || url.contains("doubleclick") -> false
// Whitelist domains
!url.contains("myapp.com") && !url.contains("cdn.myapp.com") -> false
// Allow all other HTTPS navigation
else -> true
}
}
)// Android-specific WebView settings can be configured
// through the native platform implementation// iOS WKWebView provides automatic dark mode support
// and native Safari features// WebView2 provides full Chromium engine compatibility
// with automatic updates through Windows Update// macOS WKWebView integrates seamlessly with system
// appearance and accessibility featuresComprehensive support for modern web technologies across all platforms:
| Feature | Android | iOS | Windows | macOS |
|---|---|---|---|---|
| HTML5 | β | β | β | β |
| CSS3 | β | β | β | β |
| ES6+ JavaScript | β | β | β | β |
| WebGL | β | β | β | β |
| WebAssembly | β | β | β | β |
| WebSockets | β | β | β | β |
| Service Workers | β | β | β | β |
| Local Storage | β | β | β | β |
| IndexedDB | β | β | β | β |
| WebRTC | β | β | β | β |
| Canvas API | β | β | β | β |
| Web Audio API | β | β | β | β |
| Geolocation* | β | β | β | β |
| Media Capture | β | β | β | β |
*Requires appropriate platform permissions
Native WebView vs. Embedded Browser Solutions:
| Metric | Native WebView | Embedded Chromium |
|---|---|---|
| App Size Increase | ~2MB | ~100-150MB |
| Memory Footprint | Low (Shared) | High (Isolated) |
| Startup Time | Fast | Slow |
| System Integration | Native | Sandboxed |
| Security Updates | Automatic (OS) | Manual (Developer) |
| Platform Consistency | Native UX | Consistent but foreign |
| Battery Impact | Optimized | Higher |
WebView not updating:
// Users may need to update Android System WebView from Play Store
// Your app should handle this gracefullyClear WebView cache:
// Platform-specific cache clearing can be implemented
// through the native implementationContent not loading:
Info.plist
Programmatic check:
fun isWebView2Available(): Boolean {
// Check if WebView2 runtime is installed
return true // Implementation depends on platform detection
}Minimum version check:
fun checkMacOSVersion(): Boolean {
val osVersion = System.getProperty("os.version")
// macOS 10.15+ required
return true
}JNA Loading Errors:
dependencies {
implementation("net.java.dev.jna:jna:5.13.0")
implementation("net.java.dev.jna:jna-platform:5.13.0")
}Memory Leaks:
onDisposed callback for cleanup@Test
fun testWebViewStateInitialization() = runComposeUiTest {
var state: PlatformWebViewState? = null
setContent {
state = rememberPlatformWebViewState(
url = "https://example.com",
javaScriptEnabled = true
)
}
assertNotNull(state)
}@Test
fun testNavigationBlocking() = runComposeUiTest {
var navigationBlocked = false
setContent {
val state = rememberPlatformWebViewState(
url = "https://example.com",
onNavigating = { url ->
if (url.contains("malicious")) {
navigationBlocked = true
false
} else {
true
}
}
)
PlatformWebView(state = state)
}
// Verify navigation blocking works
assertTrue(navigationBlocked || !navigationBlocked) // Placeholder
}@Test
fun testWebViewLifecycle() = runComposeUiTest {
var created = false
var disposed = false
setContent {
PlatformWebView(
url = "https://example.com",
onCreated = { created = true },
onDisposed = { disposed = true }
)
}
waitUntil(timeoutMillis = 5000) { created }
setContent { /* Remove WebView */ }
waitUntil(timeoutMillis = 3000) { disposed }
}Compose Multiplatform Native WebView is developed and maintained by Saral Apps Pvt. Ltd., a Nepal-based technology company specializing in innovative software solutions and custom digital experiences.
Based in Kathmandu, Nepal, Saral Apps focuses on creating scalable, interactive solutions for businesses and educational institutions across various industries.
Core Services:
Technology Stack:
Our solutions power educational and business platforms across Nepal:
We build production-ready, open-source libraries that solve real problems for the developer community. Our tools are battle-tested in commercial applications and continuously improved based on real-world usage.
We welcome contributions from the community! Whether it's bug reports, feature requests, documentation improvements, or code contributions, your input helps make this library better for everyone.
We're actively seeking contributors to implement Linux support!
The library currently supports Android, iOS, Windows, and macOS. We'd love to extend support to Linux using native WebView solutions.
Potential Linux WebView Approaches:
What We're Looking For:
Fork the repository
git clone https://github.com/saral-apps/composemultiplatformwebview
cd composemultiplatformwebviewCreate a feature branch
git checkout -b feature/your-feature-nameMake your changes
Test thoroughly
Submit a Pull Request
Found a bug or have a feature request? Please open an issue on GitHub:
For Linux support specifically, reach out to info@saralapps.com or start a discussion on GitHub. We're happy to provide guidance and technical support throughout development.
Get help and connect with other developers:
Copyright 2025 Saral Apps Pvt. Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
If you find Compose Multiplatform Native WebView useful, please:
Built with β€οΈ by Saral Apps Pvt. Ltd. in Kathmandu, Nepal
Empowering developers worldwide with production-ready, native-quality tools for Kotlin Multiplatform
kotlin multiplatform, compose multiplatform, webview, android webview, ios wkwebview, windows webview2, macos wkwebview, cross-platform webview, native webview, compose desktop, kotlin native, multiplatform library, web integration, javascript bridge, kotlin compose, mobile development, desktop development, web view component, kmp library, compose ui
A powerful native WebView integration for Compose Multiplatform that provides seamless web content rendering across Android, iOS, Desktop (Windows/macOS), and JVM platforms. Built with native platform APIs for superior performance, authentic user experience, and zero external dependencies.
True Native Integration β Leverages each platform's native WebView components: Android WebView, iOS WKWebView, Windows WebView2 (Chromium), and macOS WKWebView for authentic platform behavior.
Universal Cross-Platform Support β Single API that works seamlessly across Android, iOS, Windows, macOS, and Linux (community-supported).
Zero Bundled Dependencies β No embedded browsers or heavy dependencies. Uses the web rendering technology already present on each platform.
Production-Ready Performance β Battle-tested in real-world applications with native-level performance and memory efficiency.
Compose-First Design β Idiomatic Kotlin Multiplatform API built specifically for Compose developers with reactive state management.
Enterprise-Grade Security β Inherits security features and automatic updates from each platform's native WebView implementation.
Add the dependency to your build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("com.saralapps:composemultiplatformwebview:0.1.4")
}
}
}Android:
iOS:
Windows (x64):
macOS:
Linux:
import com.saralapps.composemultiplatformwebview.PlatformWebView
@Composable
fun App() {
PlatformWebView(
url = "https://kotlinlang.org",
modifier = Modifier.fillMaxSize()
)
}@Composable
fun WebViewWithState() {
val webViewState = rememberPlatformWebViewState(
url = "https://github.com",
javaScriptEnabled = true,
allowsFileAccess = true
)
PlatformWebView(
state = webViewState,
modifier = Modifier.fillMaxSize(),
onUrlChanged = { newUrl ->
println("Navigated to: $newUrl")
}
)
}@Composable
fun InteractiveBrowser() {
var currentUrl by remember { mutableStateOf("https://example.com") }
var isLoading by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize()) {
// Navigation bar
Row(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
TextField(
value = currentUrl,
onValueChange = { currentUrl = it },
modifier = Modifier.weight(1f),
placeholder = { Text("Enter URL") },
singleLine = true
)
Button(
onClick = { /* Trigger navigation */ },
enabled = !isLoading
) {
Text("Go")
}
}
// Loading indicator
if (isLoading) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth()
)
}
// WebView
PlatformWebView(
url = currentUrl,
modifier = Modifier.weight(1f),
javaScriptEnabled = true,
onUrlChanged = { newUrl ->
currentUrl = newUrl
isLoading = false
},
onNavigating = { url ->
isLoading = true
true // Allow navigation
}
)
}
}@Composable
fun WebViewWithFallback() {
PlatformWebView(
url = "https://example.com",
modifier = Modifier.fillMaxSize(),
onUnavailable = { availability ->
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
when (availability) {
is WebViewAvailability.NotInstalled -> {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("WebView not available on this device")
Button(onClick = { /* Handle installation */ }) {
Text("Install WebView2")
}
}
}
is WebViewAvailability.Error -> {
Text("Error: ${availability.message}")
}
else -> {
Text("WebView unavailable")
}
}
}
}
)
}Manages the internal state and configuration of the WebView:
val webViewState = rememberPlatformWebViewState(
url = "https://example.com",
javaScriptEnabled = true,
allowsFileAccess = false,
onNavigating = { url ->
// Return true to allow, false to block navigation
url.startsWith("https://")
}
)Parameters:
url: String? - Initial URL to load (optional)javaScriptEnabled: Boolean - Enable/disable JavaScript execution (default: true)allowsFileAccess: Boolean - Allow/deny local file access (default: true)onNavigating: ((String) -> Boolean)? - Navigation interception callbackTwo variants available for different use cases:
Best for complex scenarios requiring state management:
@Composable
fun PlatformWebView(
state: PlatformWebViewState,
modifier: Modifier = Modifier,
placeholderColor: Color = Color.White,
onUrlChanged: ((String) -> Unit)? = null,
onCreated: (() -> Unit)? = null,
onDisposed: (() -> Unit)? = null,
onUnavailable: @Composable ((WebViewAvailability) -> Unit)? = null
)Perfect for straightforward WebView integration:
@Composable
fun PlatformWebView(
url: String,
modifier: Modifier = Modifier,
javaScriptEnabled: Boolean = true,
allowsFileAccess: Boolean = true,
placeholderColor: Color = Color.White,
onUrlChanged: ((String) -> Unit)? = null,
onNavigating: ((String) -> Boolean)? = null,
onCreated: (() -> Unit)? = null,
onDisposed: (() -> Unit)? = null,
onUnavailable: @Composable ((WebViewAvailability) -> Unit)? = null
)| Parameter | Type | Description |
|---|---|---|
modifier |
Modifier |
Compose modifier for layout and styling |
placeholderColor |
Color |
Background color during WebView initialization |
onUrlChanged |
((String) -> Unit)? |
Callback triggered when URL changes |
onNavigating |
((String) -> Boolean)? |
Pre-navigation callback; return false to block |
onCreated |
(() -> Unit)? |
Callback when WebView is successfully created |
onDisposed |
(() -> Unit)? |
Callback when WebView is disposed |
onUnavailable |
@Composable ((WebViewAvailability) -> Unit)? |
Composable shown when WebView unavailable |
sealed class WebViewAvailability {
object Available : WebViewAvailability()
object NotInstalled : WebViewAvailability()
data class Error(val message: String) : WebViewAvailability()
}@Composable
fun SecureWebView(url: String) {
val webViewState = rememberPlatformWebViewState(
url = url,
javaScriptEnabled = false, // Disable for untrusted content
allowsFileAccess = false, // Prevent file access
onNavigating = { navigationUrl ->
// Whitelist allowed domains
val allowedDomains = listOf("example.com", "api.example.com")
val uri = URI(navigationUrl)
allowedDomains.any { uri.host?.endsWith(it) == true }
}
)
PlatformWebView(
state = webViewState,
modifier = Modifier.fillMaxSize()
)
}@Composable
fun DynamicContentViewer() {
var selectedContent by remember {
mutableStateOf("https://kotlinlang.org")
}
val contentOptions = mapOf(
"Kotlin" to "https://kotlinlang.org",
"Compose" to "https://www.jetbrains.com/lp/compose-multiplatform/",
"GitHub" to "https://github.com"
)
Column(modifier = Modifier.fillMaxSize()) {
// Content selector
Row(
modifier = Modifier.fillMaxWidth().padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
contentOptions.forEach { (label, url) ->
Button(
onClick = { selectedContent = url },
colors = ButtonDefaults.buttonColors(
containerColor = if (selectedContent == url)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.secondary
)
) {
Text(label)
}
}
}
// WebView with dynamic content
PlatformWebView(
url = selectedContent,
modifier = Modifier.weight(1f),
javaScriptEnabled = true
)
}
}@Composable
fun TabbedBrowser() {
var tabs by remember {
mutableStateOf(listOf(
"https://kotlinlang.org",
"https://github.com"
))
}
var selectedTab by remember { mutableStateOf(0) }
Column(modifier = Modifier.fillMaxSize()) {
// Tab row
ScrollableTabRow(selectedTabIndex = selectedTab) {
tabs.forEachIndexed { index, url ->
Tab(
selected = selectedTab == index,
onClick = { selectedTab = index },
text = {
Text(
url.substringAfter("://")
.substringBefore("/")
.take(20)
)
}
)
}
}
// Active tab content
PlatformWebView(
url = tabs[selectedTab],
modifier = Modifier.weight(1f),
javaScriptEnabled = true,
onUrlChanged = { newUrl ->
tabs = tabs.toMutableList().apply {
set(selectedTab, newUrl)
}
}
)
}
}@Composable
fun WebViewWithLifecycle() {
var webViewActive by remember { mutableStateOf(false) }
var pageTitle by remember { mutableStateOf("") }
PlatformWebView(
url = "https://example.com",
modifier = Modifier.fillMaxSize(),
onCreated = {
webViewActive = true
println("WebView initialized successfully")
},
onUrlChanged = { url ->
// Update page title or handle navigation
pageTitle = url.substringAfter("://").substringBefore("/")
},
onDisposed = {
webViewActive = false
println("WebView resources released")
}
)
}Control JavaScript execution based on content trust level:
// Untrusted external content
PlatformWebView(
url = "https://untrusted-site.com",
javaScriptEnabled = false, // Disable JavaScript
allowsFileAccess = false,
modifier = Modifier.fillMaxSize()
)
// Trusted application content
PlatformWebView(
url = "https://your-app.com",
javaScriptEnabled = true,
modifier = Modifier.fillMaxSize()
)// Web content (recommended: deny file access)
val webState = rememberPlatformWebViewState(
url = "https://example.com",
allowsFileAccess = false
)
// Local HTML content (required: allow file access)
val localState = rememberPlatformWebViewState(
url = "file:///android_asset/index.html",
allowsFileAccess = true
)val secureWebViewState = rememberPlatformWebViewState(
url = "https://myapp.com",
onNavigating = { url ->
when {
// Block non-HTTPS
!url.startsWith("https://") -> false
// Block tracking and ads
url.contains("analytics") || url.contains("doubleclick") -> false
// Whitelist domains
!url.contains("myapp.com") && !url.contains("cdn.myapp.com") -> false
// Allow all other HTTPS navigation
else -> true
}
}
)// Android-specific WebView settings can be configured
// through the native platform implementation// iOS WKWebView provides automatic dark mode support
// and native Safari features// WebView2 provides full Chromium engine compatibility
// with automatic updates through Windows Update// macOS WKWebView integrates seamlessly with system
// appearance and accessibility featuresComprehensive support for modern web technologies across all platforms:
| Feature | Android | iOS | Windows | macOS |
|---|---|---|---|---|
| HTML5 | β | β | β | β |
| CSS3 | β | β | β | β |
| ES6+ JavaScript | β | β | β | β |
| WebGL | β | β | β | β |
| WebAssembly | β | β | β | β |
| WebSockets | β | β | β | β |
| Service Workers | β | β | β | β |
| Local Storage | β | β | β | β |
| IndexedDB | β | β | β | β |
| WebRTC | β | β | β | β |
| Canvas API | β | β | β | β |
| Web Audio API | β | β | β | β |
| Geolocation* | β | β | β | β |
| Media Capture | β | β | β | β |
*Requires appropriate platform permissions
Native WebView vs. Embedded Browser Solutions:
| Metric | Native WebView | Embedded Chromium |
|---|---|---|
| App Size Increase | ~2MB | ~100-150MB |
| Memory Footprint | Low (Shared) | High (Isolated) |
| Startup Time | Fast | Slow |
| System Integration | Native | Sandboxed |
| Security Updates | Automatic (OS) | Manual (Developer) |
| Platform Consistency | Native UX | Consistent but foreign |
| Battery Impact | Optimized | Higher |
WebView not updating:
// Users may need to update Android System WebView from Play Store
// Your app should handle this gracefullyClear WebView cache:
// Platform-specific cache clearing can be implemented
// through the native implementationContent not loading:
Info.plist
Programmatic check:
fun isWebView2Available(): Boolean {
// Check if WebView2 runtime is installed
return true // Implementation depends on platform detection
}Minimum version check:
fun checkMacOSVersion(): Boolean {
val osVersion = System.getProperty("os.version")
// macOS 10.15+ required
return true
}JNA Loading Errors:
dependencies {
implementation("net.java.dev.jna:jna:5.13.0")
implementation("net.java.dev.jna:jna-platform:5.13.0")
}Memory Leaks:
onDisposed callback for cleanup@Test
fun testWebViewStateInitialization() = runComposeUiTest {
var state: PlatformWebViewState? = null
setContent {
state = rememberPlatformWebViewState(
url = "https://example.com",
javaScriptEnabled = true
)
}
assertNotNull(state)
}@Test
fun testNavigationBlocking() = runComposeUiTest {
var navigationBlocked = false
setContent {
val state = rememberPlatformWebViewState(
url = "https://example.com",
onNavigating = { url ->
if (url.contains("malicious")) {
navigationBlocked = true
false
} else {
true
}
}
)
PlatformWebView(state = state)
}
// Verify navigation blocking works
assertTrue(navigationBlocked || !navigationBlocked) // Placeholder
}@Test
fun testWebViewLifecycle() = runComposeUiTest {
var created = false
var disposed = false
setContent {
PlatformWebView(
url = "https://example.com",
onCreated = { created = true },
onDisposed = { disposed = true }
)
}
waitUntil(timeoutMillis = 5000) { created }
setContent { /* Remove WebView */ }
waitUntil(timeoutMillis = 3000) { disposed }
}Compose Multiplatform Native WebView is developed and maintained by Saral Apps Pvt. Ltd., a Nepal-based technology company specializing in innovative software solutions and custom digital experiences.
Based in Kathmandu, Nepal, Saral Apps focuses on creating scalable, interactive solutions for businesses and educational institutions across various industries.
Core Services:
Technology Stack:
Our solutions power educational and business platforms across Nepal:
We build production-ready, open-source libraries that solve real problems for the developer community. Our tools are battle-tested in commercial applications and continuously improved based on real-world usage.
We welcome contributions from the community! Whether it's bug reports, feature requests, documentation improvements, or code contributions, your input helps make this library better for everyone.
We're actively seeking contributors to implement Linux support!
The library currently supports Android, iOS, Windows, and macOS. We'd love to extend support to Linux using native WebView solutions.
Potential Linux WebView Approaches:
What We're Looking For:
Fork the repository
git clone https://github.com/saral-apps/composemultiplatformwebview
cd composemultiplatformwebviewCreate a feature branch
git checkout -b feature/your-feature-nameMake your changes
Test thoroughly
Submit a Pull Request
Found a bug or have a feature request? Please open an issue on GitHub:
For Linux support specifically, reach out to info@saralapps.com or start a discussion on GitHub. We're happy to provide guidance and technical support throughout development.
Get help and connect with other developers:
Copyright 2025 Saral Apps Pvt. Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
If you find Compose Multiplatform Native WebView useful, please:
Built with β€οΈ by Saral Apps Pvt. Ltd. in Kathmandu, Nepal
Empowering developers worldwide with production-ready, native-quality tools for Kotlin Multiplatform
kotlin multiplatform, compose multiplatform, webview, android webview, ios wkwebview, windows webview2, macos wkwebview, cross-platform webview, native webview, compose desktop, kotlin native, multiplatform library, web integration, javascript bridge, kotlin compose, mobile development, desktop development, web view component, kmp library, compose ui