/compose-webview-multiplatform

WebView for JetBrains Compose Multiplatform. Support Android, iOS, and Desktop platforms.

Primary LanguageKotlinApache License 2.0Apache-2.0

WebView for JetBrains Compose Multiplatform

Maven Central Kotlin Compose Multiplatform

badge-android badge-ios badge-desktop

Note This library is built using the compose multiplatform library template. It supports automatic package publishing, documentation, and code style checking.

This library can be considered as the Multiplatform version of Accompanist Web library. It provides the basic WebView functionalities for JetBrains Compose Multiplatform, which supports loading URLs, HTML, and post data. Currently, it supports the platforms of Android, iOS, and Desktop.

  • The Android implementation of this library relies on the web module from the Accompanist Library.

    • Note: it has been deprecated in version 0.33.1-alpha. Thus I created a fork of it and used it as the base for this library. If you just want to use the WebView in Jetpack Compose, please visit this repo: https://github.com/KevinnZou/compose-webview.
  • The iOS implementation of this library relies on WKWebView.

  • The Desktop implementation of this library relies on JavaFX WebView for version <= 1.2.0.

    • Thanks to @DATL4G, starting from version 1.3.0, we switched to Java CEF Browser for better performance.
    • Note: After switching to JCEF, developers need to configure it for the desktop app. Please see the README.desktop.md for more details.

Basic Usage

To use this widget there are two key APIs that are needed: WebView, which provides the layout, and rememberWebViewState(url) which provides some remembered state including the URL to display.

The basic usage is as follows:

val state = rememberWebViewState("https://example.com")

WebView(state)

This will display a WebView in your Compose layout that shows the URL provided.

WebView State

This library provides a WebViewState class as a state holder to hold the state for the WebView.

class WebViewState(webContent: WebContent) {
    var lastLoadedUrl: String? by mutableStateOf(null)
        internal set

    /**
     *  The content being loaded by the WebView
     */
    var content: WebContent by mutableStateOf(webContent)

    /**
     * Whether the WebView is currently [LoadingState.Loading] data in its main frame (along with
     * progress) or the data loading has [LoadingState.Finished]. See [LoadingState]
     */
    var loadingState: LoadingState by mutableStateOf(LoadingState.Initializing)
        internal set

    /**
     * Whether the webview is currently loading data in its main frame
     */
    val isLoading: Boolean
        get() = loadingState !is LoadingState.Finished

    /**
     * The title received from the loaded content of the current page
     */
    var pageTitle: String? by mutableStateOf(null)
        internal set

    /**
     * A list for errors captured in the last load. Reset when a new page is loaded.
     * Errors could be from any resource (iframe, image, etc.), not just for the main page.
     * For more fine grained control use the OnError callback of the WebView.
     */
    val errorsForCurrentRequest: SnapshotStateList<WebViewError> = mutableStateListOf()

    /**
     * Custom Settings for WebView.
     */
    val webSettings: WebSettings by mutableStateOf(WebSettings())

    // We need access to this in the state saver. An internal DisposableEffect or AndroidView
    // onDestroy is called after the state saver and so can't be used.
    internal var webView by mutableStateOf<IWebView?>(null)
}

It can be created using the rememberWebViewState function, which can be remembered across Compositions.

val state = rememberWebViewState("https://github.com/KevinnZou/compose-webview-multiplatform")

/**
 * Creates a WebView state that is remembered across Compositions.
 *
 * @param url The url to load in the WebView
 * @param additionalHttpHeaders Optional, additional HTTP headers that are passed to [WebView.loadUrl].
 *                              Note that these headers are used for all subsequent requests of the WebView.
 */
@Composable
fun rememberWebViewState(
    url: String,
    additionalHttpHeaders: Map<String, String> = emptyMap()
)

/**
 * Creates a WebView state that is remembered across Compositions.
 *
 * @param data The uri to load in the WebView
 * @param baseUrl The URL to use as the page's base URL.
 * @param encoding The encoding of the data in the string.
 * @param mimeType The MIME type of the data in the string.
 * @param historyUrl The history URL for the loaded HTML. Leave null to use about:blank.
 */
@Composable
fun rememberWebViewStateWithHTMLData(
  data: String,
  baseUrl: String? = null,
  encoding: String = "utf-8",
  mimeType: String? = null,
  historyUrl: String? = null
)

/**
 * Creates a WebView state that is remembered across Compositions.
 *
 * @param fileName The file to load in the WebView
 */
@Composable
fun rememberWebViewStateWithHTMLFile(
  fileName: String,
)

Developers can use the WebViewState to get the loading information of the WebView, such as the loading progress, the loading status, and the URL of the current page.

Column {
    val state = rememberWebViewState("https://github.com/KevinnZou/compose-webview-multiplatform")

    Text(text = "${state.pageTitle}")
    val loadingState = state.loadingState
    if (loadingState is LoadingState.Loading) {
        LinearProgressIndicator(
            progress = loadingState.progress,
            modifier = Modifier.fillMaxWidth()
        )
    }
    WebView(
        state
    )
}

WebView Navigator

This library provides a WebViewNavigator class to control over the navigation of a WebView from outside the composable. E.g.for performing a back navigation in response to the user clicking the "up" button in a TopAppBar. It can be used to load a new URL, evaluate the JavaScript, and go back and forward in the history.

class WebViewNavigator(private val coroutineScope: CoroutineScope) {

    /**
     * True when the web view is able to navigate backwards, false otherwise.
     */
    var canGoBack: Boolean by mutableStateOf(false)
        internal set

    /**
     * True when the web view is able to navigate forwards, false otherwise.
     */
    var canGoForward: Boolean by mutableStateOf(false)
        internal set

    fun loadUrl(url: String, additionalHttpHeaders: Map<String, String> = emptyMap()) {}

    fun loadHtml(
        html: String,
        baseUrl: String? = null,
        mimeType: String? = null,
        encoding: String? = "utf-8",
        historyUrl: String? = null
    ) {
    }

    fun postUrl(
        url: String,
        postData: ByteArray
    ) {
    }

    fun evaluateJavaScript(script: String, callback: ((String) -> Unit)? = null)

    /**
     * Navigates the webview back to the previous page.
     */
    fun navigateBack() {}

    /**
     * Navigates the webview forward after going back from a page.
     */
    fun navigateForward() {}

    /**
     * Reloads the current page in the webview.
     */
    fun reload() {}

    /**
     * Stops the current page load (if one is loading).
     */
    fun stopLoading() {}
}

It can be created using the rememberWebViewNavigator function, which can be remembered across Compositions.

val navigator = rememberWebViewNavigator()

@Composable
fun rememberWebViewNavigator(
    coroutineScope: CoroutineScope = rememberCoroutineScope()
): WebViewNavigator = remember(coroutineScope) { WebViewNavigator(coroutineScope) }

Developers can use the WebViewNavigator to control the navigation of the WebView.

val navigator = rememberWebViewNavigator()

Column {
    val state = rememberWebViewState("https://example.com")
    val navigator = rememberWebViewNavigator()

    TopAppBar(
        title = { Text(text = "WebView Sample") },
        navigationIcon = {
            if (navigator.canGoBack) {
                IconButton(onClick = { navigator.navigateBack() }) {
                    Icon(
                        imageVector = Icons.Default.ArrowBack,
                        contentDescription = "Back"
                    )
                }
            }
        }
    )
    Text(text = "${state.pageTitle}")
    val loadingState = state.loadingState
    if (loadingState is LoadingState.Loading) {
        LinearProgressIndicator(
            progress = loadingState.progress,
            modifier = Modifier.fillMaxWidth()
        )
    }
    WebView(
        state = state,
        navigator = navigator
    )
}

WebSettings

Starting from version 1.3.0, this library allows users to customize web settings. There are some common web settings that can be shared across different platforms, such as isJavaScriptEnabled and userAgent.

class WebSettings {
    var isJavaScriptEnabled = true

    var customUserAgentString: String? = null

    /**
     * Android platform specific settings
     */
    val androidWebSettings = PlatformWebSettings.AndroidWebSettings()

    /**
     * Desktop platform specific settings
     */
    val desktopWebSettings = PlatformWebSettings.DesktopWebSettings()

    /**
     * iOS platform specific settings
     */
    val iOSWebSettings = PlatformWebSettings.IOSWebSettings

}

For platform specific settings, this library provides the PlatformWebSettings. These settings will only be applied to the respective platforms and do not affect other platforms.

sealed class PlatformWebSettings {
    data class AndroidWebSettings(
        /**
         * whether the WebView should support zooming using its on-screen zoom
         * controls and gestures. The particular zoom mechanisms that should be used
         * can be set with {@link #setBuiltInZoomControls}. This setting does not
         * affect zooming performed using the {@link WebView#zoomIn()} and
         * {@link WebView#zoomOut()} methods. The default is {@code true}.
         *
         * @param support whether the WebView should support zoom
         */
        var supportZoom: Boolean = true,

        /**
         * whether Safe Browsing is enabled. Safe Browsing allows WebView to
         * protect against malware and phishing attacks by verifying the links.
         */
        var safeBrowsingEnabled: Boolean = true,

        // .....
    ) : PlatformWebSettings()

    data class DesktopWebSettings(
        var offScreenRendering: Boolean = false,
        var transparent: Boolean = false,
    ) : PlatformWebSettings()

    data object IOSWebSettings : PlatformWebSettings()
}

Developers can configure custom settings in the shared code in the following way:

val webViewState = rememberWebViewStateWithHTMLData(
    data = html
)
webViewState.webSettings.apply {
    isJavaScriptEnabled = true
    androidWebSettings.apply {
        isAlgorithmicDarkeningAllowed = true
        safeBrowsingEnabled = true
    }
}

API

The complete API of this library is as follows:

/**
 *
 * A wrapper around the Android View WebView to provide a basic WebView composable.
 *
 * @param state The webview state holder where the Uri to load is defined.
 * @param modifier A compose modifier
 * @param captureBackPresses Set to true to have this Composable capture back presses and navigate
 * the WebView back.
 * @param navigator An optional navigator object that can be used to control the WebView's
 * navigation from outside the composable.
 * @param onCreated Called when the WebView is first created.
 * @param onDispose Called when the WebView is destroyed.
 * @sample sample.BasicWebViewSample
 */
@Composable
fun WebView(
    state: WebViewState,
    modifier: Modifier = Modifier,
    captureBackPresses: Boolean = true,
    navigator: WebViewNavigator = rememberWebViewNavigator(),
    onCreated: () -> Unit = {},
    onDispose: () -> Unit = {},
)

Example

A simple example would be like this:

@Composable
internal fun WebViewSample() {
    MaterialTheme {
        val webViewState = rememberWebViewState("https://github.com/KevinnZou/compose-webview-multiplatform")
        Column(Modifier.fillMaxSize()) {
            val text = webViewState.let {
                "${it.pageTitle ?: ""} ${it.loadingState} ${it.lastLoadedUrl ?: ""}"
            }
            Text(text)
            WebView(
                state = webViewState,
                modifier = Modifier.fillMaxSize()
            )
        }

    }
}

For a full example, please refers to BasicWebViewSample

Download

Maven Central

You can add this library to your project using Gradle.

Multiplatform

To add to a multiplatform project, add the dependency to the common source-set:

repositories {
    mavenCentral()
}

kotlin {
    sourceSets {
        commonMain {
            dependencies {
                // use api since the desktop app need to access the Cef to initialize it.
                api("io.github.kevinnzou:compose-webview-multiplatform:1.6.0")
            }
        }
    }
}

Single Platform

For an Android only project, you directly can use my another library. Add the dependency to app level build.gradle.kts:

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.github.KevinnZou:compose-webview:0.33.3")
}