Load a web page with WebView in Android Studio | Jetpack Compose

Video thumbnail

Previously, we saw how to display a YouTube video in the Android emulator. Following that same philosophy, we will now learn how to view the content of a web page directly on our device, functioning as a kind of integrated browser. 

Is the WebView class a browser?

WebView is not a browser since it does not have elements found in browsers like an address bar or other navigation controls; it only allows displaying a web page within a native Android application.

However, with the WebView class, we can decide whether we want to show the content inside the application or in a web browser, as we will see later.

Building Web Apps with WebView

To add a WebView to our Android application, we must perform a series of configurations that we will see below:

Enable Internet permission in the AndroidManifest

One of the first things we must do is request the necessary permission in our AndroidManifest so that the application can connect to the Internet and download HTML content:

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

The modern way with Jetpack Compose

With the arrival of Jetpack Compose, the way to build user interfaces in Android has shifted to a declarative approach. To integrate a WebView into this new world, we use the AndroidView composable, which allows us to embed traditional Android views (XML-based) within a Compose UI.

app/src/main/AndroidManifest.xml

Enable Internet permission in the AndroidManifest

Just like in the traditional method, it is essential to request INTERNET permission in the AndroidManifest.xml file:

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

Creating the Composable for the WebView

Now, we create a Composable function that encapsulates the WebView. This allows us to easily reuse it anywhere in our application.

1. Factory Configuration

We use AndroidView to inject the classic Android view into the Compose environment. We specify that it occupies the full screen size using a Modifier.

JavaScript: It is fundamental to enable JavaScript (settings.javaScriptEnabled = true) so that the page functions correctly if it has interactive elements or script-dependent layouts.

URL Loading: We use the loadUrl function, passing the parameter received by our Composable.

package com.example.myproyectandroid

import android.os.Bundle
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.example.myproyectandroid.ui.theme.MyProyectAndroidTheme

class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            MyProyectAndroidTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Column(modifier = Modifier.padding(innerPadding)) {
                        Greeting(
                            name = "Android",
                            modifier = Modifier.padding(innerPadding)
                        )
                        MyWebView(url = "https://www.desarrollolibre.net/")
                    }
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello $name!",
        modifier = modifier.padding(8.dp)
    )
}

@Composable
fun MyWebView(url: String) {
    AndroidView(
        modifier = Modifier.fillMaxSize(), // Ocupa toda la pantalla
        factory = { context ->
            WebView(context).apply {
                // Configuración necesaria
                settings.javaScriptEnabled = true // Permite que funcionen sitios modernos
                webViewClient =
                    WebViewClient() // Abre los enlaces dentro de la app, no en el navegador

                loadUrl(url)
            }
        },
        update = { webView ->
            // Si la URL cambia, la cargamos aquí
            webView.loadUrl(url)
        }
    )
}

2. The Update Block

The update parameter is very useful (though optional). If the URL changes dynamically in your application, this block will handle updating the content automatically without needing to recreate the entire view.

update = { webView ->
            // Si la URL cambia, la cargamos aquí
            webView.loadUrl(url)
        }

Event Control with WebViewClient

The WebViewClient allows us to define specific client events and behaviors. For example:

  • Internal navigation: Prevent the system from opening the phone's external browser when a link is clicked, keeping the user within our app.
  • Loading cycle: Execute actions when the page starts loading or when it finishes (ideal for showing a loading spinner or progress bar).
  • Error management: Handle connection failures or block access to specific URLs through custom checks.

1. Prevent links from opening in Chrome

webViewClient = object : WebViewClient() {
   override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
       // Retornar 'false' significa: "Yo me encargo, cárgalo aquí mismo"
       return false 
   }
}

2. Show "Loading" (ProgressBar)

webViewClient = object : WebViewClient() {
   override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
       super.onPageStarted(view, url, favicon)
       // Aquí podrías poner: isLoading = true
   }
   override fun onPageFinished(view: WebView?, url: String?) {
       super.onPageFinished(view, url)
       // Aquí podrías poner: isLoading = false
   }
}

3. Handle connection errors

override fun onReceivedError(
   view: WebView?,
   request: WebResourceRequest?,
   error: WebResourceError?
) {
   // Aquí puedes cargar un archivo HTML local de error:
   view?.loadUrl("file:///android_asset/error.html")
}

4. Block specific URLs

override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
   val url = request?.url.toString()
   return if (url.contains("misitio.com")) {
       false // Permitir
   } else {
       true // Bloquear (no hace nada al hacer clic)
   }
}

⚙️ Advanced Settings (Settings)

Through settings, you can customize the browsing experience according to your needs:

  • Desktop view: Force the "Desktop" version of the site.
  • Screen fitting: Scale content to occupy exactly the mobile width.
  • Zoom Control: Enable or remove "pinch" gestures to zoom in/out, or hide zoom control buttons.
  • Cache and Storage: Enable local cache storage to improve speed.
  • Security: Prevent scripts from opening pop-up windows automatically.

1. Display and Zoom Optimization

settings.apply {
   // Hace que la página se vea como en un navegador de escritorio si es muy grande
   useWideViewPort = true 
   
   // Ajusta el contenido al ancho de la pantalla del móvil automáticamente
   loadWithOverviewMode = true 
   
   // Habilita los controles de zoom (los botones + y -)
   builtInZoomControls = true 
   
   // Oculta esos botones de zoom feos pero permite el gesto de "pinza" con los dedos
   displayZoomControls = false 
}

2. Storage and Performance (Cache)

settings.apply {
   // Habilita el almacenamiento local (como las bases de datos de las webs modernas)
   domStorageEnabled = true 
   
   // Permite que la web guarde archivos en el dispositivo (cache)
   databaseEnabled = true
   
   // Define cómo se comporta el cache (cargar desde internet o desde el disco)
   cacheMode = WebSettings.LOAD_DEFAULT 
}

3. Security and Privacy

settings.apply {
   // Bloquea que la web cargue imágenes desde sitios "http" si la web es "https"
   mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
   
   // Permite o deniega que el JavaScript abra ventanas nuevas (pop-ups) automáticamente
   javaScriptCanOpenWindowsAutomatically = false
}

4. Access to Files and Camera

settings.apply { // Allows web JS to access files inside the app (Be careful with this) allowFileAccess = true // Allows content loaded from a file to access other files allowContentAccess = true }

Example of "All-Terrain" configuration

settings.apply {
   // Permite que el JS de la web acceda a archivos dentro de la app (Cuidado con esto)
   allowFileAccess = true
   
   // Permite que el contenido cargado desde un archivo acceda a otros archivos
   allowContentAccess = true
}

Using the WebView in your Activity (Legacy Method)

Let's look at the XML way, which is the legacy method. We call our WebViewPage composable from our MainActivity or any other screen in our application.

AndroidView(
   factory = { context ->
       WebView(context).apply {
           settings.apply {
               javaScriptEnabled = true
               domStorageEnabled = true // Clave para sitios modernos como Spotify o YouTube
               loadWithOverviewMode = true
               useWideViewPort = true
               builtInZoomControls = true
               displayZoomControls = false
               setSupportZoom(true)
               mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
           }
           webViewClient = WebViewClient()
       }
   },
   update = { webView -> webView.loadUrl(url) }
)

Code explanation:

- AndroidView: Is the key composable that allows us to use a traditional Android View inside Compose.

- factory: This block executes only once to create the WebView. Here we configure its initial parameters, such as layoutParams, webViewClient, and enabling JavaScript.

- update: This block is called every time the composable recomposes. We use it to update the view, in this case, to load a new URL if it has changed.

- WebViewClient: Just like in the traditional approach, it is crucial for handling navigation within the WebView and preventing external browsers from opening.

With these steps, you now have a functional WebView in your Jetpack Compose application, following modern Android development practices.

Loading the web page in the WebView: Layout and Activity

Once the permission is requested, we add the WebView tag in the Android activity layout:

<WebView  xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/webview" android:layout_width="fill_parent" android:layout_height="fill_parent"/>

Finally, we can add the URL of the web page we want to display using the loadUrl() method:

WebView webView = (WebView) this.findViewById(R.id.webview); webView.loadUrl("");

In our application, we will add the following URL referenced in the code:

webView.loadUrl("http://desarrollolibre.net/blog/javascript/como-hacer-una-sencilla-galeria-con-css-y-6-lineas-de-javascript");

Managing page navigation

At this point, we can run our Android application; but upon running it, we will see a screen like the following:

Webview in a browser

The idea of creating a Web App with the WebView class is not just to open the content in any of the web browsers installed on the Android device, but for it to execute within our application; for this, we will see the following block.

The WebViewClient class

As we mentioned before; by default, Android launches an application (like web browsers) to handle the previously set URL; but we can customize this behavior so that our application handles it internally; in other words, that the web page executes directly in our application.

We simply create an instance of the WebViewClient class, which is the minimum we need to display a web page inside our application:

webView.setWebViewClient(new WebViewClient());

But if we want more control, we can create our own class that extends from WebViewClient and override certain methods:

private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { return true; } }

Enabling JavaScript

JavaScript is disabled by default in the WebView, which can be a problem depending on the type of web page you want to load; that is, if our page to be loaded via the loadUrl() method needs JavaScript; we can easily enable it through the setJavaScriptEnabled() method; let's see how to use it:

First, we create a WebSettings object that allows various configurations, although in our case, we are simply interested in enabling JavaScript:

WebSettings webSettings = myWebView.getSettings();

Now we can use the setJavaScriptEnabled() method passing the boolean true as a parameter.

webSettings.setJavaScriptEnabled(true);

When running the code:

Webview in the app

Next step, create a notification in Android.

How to embed YouTube videos in Android using Jetpack Compose. Learn how to set up dependencies, manage the lifecycle with DisposableEffect, and use AndroidView.

I agree to receive announcements of interest about this Blog.

Andrés Cruz

ES En español