SkillAgentSearch skills...

ComposeWebKit

WebView for Jetpack Compose with Kotlin DSL

Install / Use

/learn @DevAtrii/ComposeWebKit
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

ComposeWebKit (Dev Atrii)

https://github.com/user-attachments/assets/ab388668-7081-4ccf-80c3-0f9b1248237d

A modern WebView wrapper for Jetpack Compose that provides easy state management, multiple instance support, and rich configuration options.

badge-Android badge-Kotlin

Features

  • 🌐 Multiple WebView instances support
  • 🔄 Pull-to-refresh functionality
  • 📱 Progress tracking
  • ⚙️ Extensive configuration options
  • 🎯 State preservation
  • ↩️ Back navigation handling
  • 🔒 SSL/Security handling
  • 🎨 Custom client configurations

Installation

Kotlin DSL

  1. Add JitPack repository in settings.gradle.kts:
dependencyResolutionManagement {
    repositories {
        // ...
        maven(url = "https://jitpack.io")
    }
}
  1. Add dependency in build.gradle.kts:
dependencies {
    implementation("com.github.DevAtrii:ComposeWebKit:3.2.1")
}

Required Permissions

Add these permissions to your AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
    android:usesCleartextTraffic="true"
    ...
>

Basic Usage

 val navigator = rememberWebViewNavigator()
val state = rememberComposeWebViewState(
    url = "https://atrii.dev",
    onBackPress = {
        if (navigator.canGoBack())
            navigator.navigateBack()
        else
            finish()
    }
) {
    configureWebSettings {
        javaScriptEnabled = true
    }
}

ComposeWebView(
    modifier = Modifier,
    state = state,
    navigator = navigator,
    pull2Refresh = false,
)

Advanced Usage

Pull-to-Refresh

var isRefreshing by rememberSaveable { mutableStateOf(false) }
val scope = rememberCoroutineScope()
val navigator = rememberWebViewNavigator()
val state = rememberComposeWebViewState(
    url = "https://atrii.dev",
    onBackPress = {
        if (navigator.canGoBack())
            navigator.navigateBack()
        else
            finish()
    }
) {
    configureWebSettings {
        javaScriptEnabled = true
        cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
    }
    configureWebChromeClients {
        onProgressChanged { _, newProgress ->
            isRefreshing = newProgress in 1..99
        }
    }
}

ComposeWebView(
    modifier = Modifier,
    state = state,
    navigator = navigator,
    pull2Refresh = true,
    isRefreshing = isRefreshing,
    onRefresh = {
        scope.launch {
            navigator.reload()
        }
    }
)

Progress Tracking

var isRefreshing by rememberSaveable { mutableStateOf(false) }
val progress = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
val navigator = rememberWebViewNavigator()
val state = rememberComposeWebViewState(
    url = "https://atrii.dev",
    onBackPress = {
        if (navigator.canGoBack())
            navigator.navigateBack()
        else
            finish()
    }
) {
    configureWebSettings {
        javaScriptEnabled = true
        cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
    }
    configureWebChromeClients {
        onProgressChanged { webView, newProgress ->
            isRefreshing = newProgress in 1..99
            scope.launch {
                progress.animateTo(newProgress.toFloat())
            }
        }
    }
}

ComposeWebView(
    modifier = Modifier,
    state = state,
    navigator = navigator,
    pull2Refresh = false
)

AnimatedVisibility(
    modifier = Modifier.fillMaxSize(),
    visible = progress.value in 1f..99f,
    enter = scaleIn() + fadeIn(),
    exit = scaleOut() + fadeOut()
) {
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        CircularProgressIndicator(
            progress = { progress.value / 100f }
        )
    }
}

WebView Configuration

val webState = rememberComposeWebViewState(
    url = "https://example.com"
) {
    configureWebSettings {
        javaScriptEnabled = true
        domStorageEnabled = true
        cacheMode = WebSettings.LOAD_DEFAULT
    }

    configureWebClients {
        onPageStarted { view, url, favicon ->
            // Handle page load start
        }
        onPageFinished { view, url ->
            // Handle page load completion
        }
    }

    configureWebChromeClients {
        onProgressChanged { _, progress ->
            // Handle progress updates
        }
        onReceivedTitle { _, title ->
            // Handle title updates
        }
    }
}

Navigation

The WebViewNavigator provides methods to control WebView navigation programmatically:

var isRefreshing by rememberSaveable { mutableStateOf(false) }
val progress = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
val navigator = rememberWebViewNavigator()
val state = rememberComposeWebViewState(
    url = "https://atrii.dev",
    onBackPress = {
        if (navigator.canGoBack())
            navigator.navigateBack()
        else
            finish()
    }
) {
    configureWebSettings {
        javaScriptEnabled = true
        cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
    }
    configureWebClients {
        onPageStarted { view, url, favicon ->
            Log.d(TAG, "onCreate: $url")
        }
    }
    configureWebChromeClients {
        onProgressChanged { webView, newProgress ->
            isRefreshing = newProgress in 1..99
            scope.launch {
                progress.animateTo(newProgress.toFloat())
            }
        }
    }
}

Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(innerPadding)
) {
    Column(
        modifier = Modifier.fillMaxWidth(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = {
            scope.launch {
                navigator.loadUrl("https://google.com")
            }
        }) {
            Text("Navigate")
        }

        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            IconButton(
                onClick = {
                    scope.launch {
                        navigator.navigateBack()
                    }
                },
            ) {
                Icon(Icons.Default.ArrowBack, "Back")
            }

            IconButton(
                onClick = {
                    scope.launch {
                        navigator.navigateForward()
                    }
                },
            ) {
                Icon(Icons.Default.ArrowForward, "Forward")
            }

            IconButton(onClick = {
                scope.launch {
                    navigator.reload()
                }
            }) {
                Icon(Icons.Default.Refresh, "Reload")
            }

            IconButton(onClick = {
                scope.launch {
                    navigator.stopLoading()
                }
            }) {
                Icon(Icons.Default.Close, "Stop")
            }
        }

    }
    Box(modifier = Modifier.weight(1f)) {
        ComposeWebView(
            modifier = Modifier,
            state = state,
            navigator = navigator,
            pull2Refresh = false
        )


        androidx.compose.animation.AnimatedVisibility(
            modifier = Modifier.fillMaxSize(),
            visible = progress.value in 1f..99f,
            enter = scaleIn() + fadeIn(),
            exit = scaleOut() + fadeOut()
        ) {
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                CircularProgressIndicator(
                    progress = { progress.value / 100f }
                )
            }
        }
    }

}

Available navigation methods:

  • navigateTo(url: String): Navigate to a specific URL
  • goBack(): Navigate back in history
  • goForward(): Navigate forward in history
  • reload(): Reload current page
  • stopLoading(): Stop current loading
  • clearHistory(): Clear navigation history
  • clearCache(): Clear cache

Multiple WebView Instances

Each ComposeWebView instance can be uniquely identified using a key:

 Column(
    modifier = Modifier
        .fillMaxSize()
        .padding(innerPadding)
) {
    ComposeWebView(
        modifier = Modifier.weight(1f),
        state = state,
        navigator = navigator,
        pull2Refresh = false,
        key = "web1"
    )

    ComposeWebView(
        modifier = Modifier.weight(1f),
        state = state,
        navigator = navigator,
        pull2Refresh = false,
        key = "web2"
    )
}

Picking Files Example

Here's how you can pick files

var filePathsCallback by remember {
    mutableStateOf<ValueCallback<Array<Uri>>?>(null)
}
var fileChooserCallbackFunction by remember {
    mutableStateOf<((Array<Uri>?) -> Unit)?>(null)
}

val launcher =
    rememberLauncherForActivityResult(contract = ActivityResultContracts.PickMultipleVisualMedia()) { result ->
        fileChooserCallbackFunction?.invoke(result.toTypedArray())
        fileChooserCallbackFunction = null
    }
...
val navigator = rememberWebViewNavigator()
val state = rememberComposeWebViewState(
    url = "https://atrii.dev/tools/image-converter/",
    onBackPress = {
        if (navigator.canGoBack())
            navigator.navigateBack()
        else
            finish()
    }
) {
    configureWebChromeClients {
        onShowFileChooser { _, webFilePathCallback, _ ->
            filePathsCallback?.onReceiveValue(null)
            filePathsCallback = webFilePathCallback

            try {
                fileChooserCallbackFunction = { uris ->
                    webFilePathCallback?.onRece
View on GitHub
GitHub Stars19
CategoryDevelopment
Updated2mo ago
Forks0

Languages

Kotlin

Security Score

75/100

Audited on Jan 14, 2026

No findings