Best Tutorial for AndroidDevelopers

Android App Development

Stay ahead with the latest tools, trends, and best practices in Android development

WebView in Jetpack Compose

WebView in Jetpack Compose - Responsive Blogger Template
WebView in Jetpack Compose: A Beginner-Friendly Tutorial

WebView in Jetpack Compose: A Beginner-Friendly Tutorial

If you're building an Android app with Jetpack Compose and want to display a website inside your app, you might be asking: "Where is the WebView in Compose?" Jetpack Compose doesn’t offer a native WebView yet — but don't worry. In this tutorial, you'll learn how to integrate the traditional WebView in your Compose app using AndroidView.

What You’ll Learn

  • How to add WebView in Jetpack Compose
  • Enable JavaScript support
  • Support file upload from web pages
  • Handle back button navigation

Step 1: Set Up Your Project

Create a new project in Android Studio using the Empty Compose Activity template. Then open your AndroidManifest.xml and add this permission:

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

This lets your app access websites from the internet.

Step 2: Add WebView Using AndroidView

Jetpack Compose provides a helper called AndroidView to use classic Android views like WebView.

@Composable
fun SimpleWebView(urlToRender: String) {
    AndroidView(factory = { context ->
        WebView(context).apply {
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
            webViewClient = WebViewClient() // open links inside app
            loadUrl(urlToRender)
        }
    })
}

  • AndroidView: Allows using classic views like WebView inside Compose.
  • WebViewClient: Ensures links open inside the WebView, not in a browser.
  • loadUrl: Loads the web page URL.

Use it in your MainActivity.kt like this:

setContent {
    MaterialTheme {
        WebViewScreen(url = "https://developer.android.com/")
    }
}

Step 3: Enable JavaScript

Most websites today need JavaScript. So don’t forget to enable it:

WebView(context).apply {
    settings.javaScriptEnabled = true  // ✅ Enable JavaScript
    webViewClient = WebViewClient()
    loadUrl(urlToRender)
}

👉Step 4: File Upload Support

If your webpage has a file input, use this Composable with ActivityResultLauncher:

var filePathCallback: ValueCallback>? = null
val filePickerLauncher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.GetContent()
) { uri ->
    uri?.let {
        filePathCallback?.onReceiveValue(arrayOf(it))
        filePathCallback = null
    }
}

AndroidView(factory = { context ->
    WebView(context).apply {
        settings.javaScriptEnabled = true
        webViewClient = WebViewClient()

        webChromeClient = object : WebChromeClient() {
            override fun onShowFileChooser(
                view: WebView?,
                filePathCallbackNew: ValueCallback>?,
                fileChooserParams: FileChooserParams?
            ): Boolean {
                filePathCallback = filePathCallbackNew
                filePickerLauncher.launch("*/*")
                return true
            }
        }

        loadUrl(urlToRender)
    }
})
  • onShowFileChooser: Called when <input type="file" /> is triggered.
  • filePickerLauncher: Opens system file picker and returns the file to the WebView.

Step 5: Handle Back Button

Let’s use BackHandler to make the system back button work like a browser’s back button:

val webView = remember { WebView(LocalContext.current) }

BackHandler(enabled = webView.canGoBack()) {
    webView.goBack()
}
  • BackHandler: A Compose utility that overrides the system back button.
  • webView.canGoBack(): Checks if the user has navigated inside WebView.
  • webView.goBack(): Goes to the previous page.

Final Tips

  • Use HTTPS URLs only
  • Enable JavaScript only when necessary
  • Don’t store passwords or auto-fill in sensitive WebViews

Full working WebView code in Jetpack Compose with:

✅ WebView inside Jetpack Compose using AndroidView
✅ JavaScript support
✅ File upload support
✅ Back button navigation
✅ Loading indicator and error handling

MainActivity

package com.example.codingcompose

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.example.codingcompose.ui.theme.CodingComposeTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            CodingComposeTheme {
                /*Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }*/
                WebViewScreen(url = "https://developer.android.com/")
            }
        }
    }
}

WebViewScreen

package com.example.codingcompose

import android.annotation.SuppressLint
import android.net.Uri
import android.view.ViewGroup
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView

@SuppressLint("SetJavaScriptEnabled")
@Composable
fun WebViewScreen(url: String) {
    val context = LocalContext.current
    val webView = remember { WebView(context) }
    var isLoading by remember { mutableStateOf(true) }
    var hasError by remember { mutableStateOf(false) }
    var filePathCallback: ValueCallback>? by remember { mutableStateOf(null) }

    val filePickerLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.GetContent()
    ) { uri ->
        filePathCallback?.onReceiveValue(uri?.let { arrayOf(it) })
        filePathCallback = null
    }

    Box(modifier = Modifier.fillMaxSize()
        .systemBarsPadding()) {
        AndroidView(
            factory = {
                webView.apply {

                    layoutParams = ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT
                    )

                    settings.domStorageEnabled = true
                    settings.javaScriptEnabled = true

                    webViewClient = object : WebViewClient() {
                        override fun onPageFinished(view: WebView?, url: String?) {
                            isLoading = false
                            hasError = false
                        }

                        override fun onReceivedError(
                            view: WebView?,
                            request: WebResourceRequest?,
                            error: WebResourceError?
                        ) {
                            hasError = true
                        }
                    }

                    webChromeClient = object : WebChromeClient() {
                        override fun onShowFileChooser(
                            view: WebView?,
                            filePathCallbackNew: ValueCallback>?,
                            fileChooserParams: FileChooserParams?
                        ): Boolean {
                            filePathCallback = filePathCallbackNew
                            filePickerLauncher.launch("*/*")
                            return true
                        }
                    }

                    loadUrl(url)
                }
            },
            update = { it.loadUrl(url) },
            modifier = Modifier.fillMaxSize()
        )

        // Loading Spinner
        if (isLoading) {
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(MaterialTheme.colorScheme.background.copy(alpha = 0.6f)),
                contentAlignment = Alignment.Center
            ) {
                CircularProgressIndicator()
            }
        }

        // Error Message
        if (hasError) {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                Text("⚠️ Failed to load the page.", color = MaterialTheme.colorScheme.error)
            }
        }
    }

    // Back button handler
    BackHandler {
        if (webView.canGoBack()) {
            webView.goBack()
        } else {
            // Exit the app if no more history
            (context as? ComponentActivity)?.finish()
        }
    }
}
WebView in Jetpack Compose Screenshot

❓ Frequently Asked Questions (FAQ)

❓ 1. What is WebView in Jetpack Compose?

Jetpack Compose does not provide a native WebView composable. To display web content, the traditional Android WebView is embedded inside Compose using AndroidView.

❓ 2. Why do we need AndroidView for WebView?

WebView belongs to the classic Android View system. AndroidView acts as a bridge that allows View-based UI components to work inside Jetpack Compose layouts.

❓ 3. Is Internet permission required for WebView?

Yes, you must add the INTERNET permission in the AndroidManifest.xml to load online web pages inside a WebView.

❓ 4. How to enable JavaScript in WebView?

JavaScript can be enabled using webView.settings.javaScriptEnabled = true. This is required for modern websites to work properly.

❓ 5. Can WebView open links inside the app?

Yes. By setting a WebViewClient, all links will open inside the app instead of redirecting to an external browser.

❓ 6. How to handle back button navigation in WebView?

You can use Compose’s BackHandler to navigate backward inside the WebView history when available.

❓ 7. Is WebView supported in Jetpack Compose?

WebView is fully supported in Compose using AndroidView, but it is still a View-based component, not a native Compose element.

❓ 8. Is using WebView good for performance?

WebView is suitable for loading web content, but for heavy UI interactions, native Compose UI is recommended for better performance.

❓ 9. Any final tips when using WebView in Compose?

  • Always use HTTPS URLs where possible. 
  • Enable JavaScript only when needed. 
  • WebView inside Compose is still a classic View, so think about lifecycle and performance.

Summary

Jetpack Compose doesn’t yet have a built-in WebView, but you can easily integrate it with AndroidView. You’ve now learned how to:

  • Add WebView to Compose
  • Enable JavaScript
  • Support file inputs
  • Implement back button support

This makes your app powerful enough to load and interact with modern websites — directly from your Compose UI!

Sandeep Kumar - Android Developer

About the Author

Sandeep Kumar is an Android developer and educator who writes beginner-friendly Jetpack Compose tutorials on CodingBihar.com. His focus is on clean UI, Material Design 3, and real-world Android apps.

SkillDedication

— Python High Level Programming Language- Expert-Written Tutorials, Projects, and Tools—

Coding Bihar

Welcome To Coding Bihar👨‍🏫