Cargar una pagina web con WebView en Android Studio | Jetpack Compose

Video thumbnail

Anteriormente vimos cómo mostrar un video de YouTube en el emulador de Android. Siguiendo esa misma filosofía, ahora aprenderemos a visualizar el contenido de una página web directamente en nuestro dispositivo, funcionando como una especie de navegador integrado. 

¿La clase WebView es un navegador?

WebView no es un navegador ya que no cuenta con elementos presentes en navegadores como barra de direcciones u otros controles de navegación; solamente permite mostrar una página web en una aplicación nativa Android.

Aunque con la clase WebView podemos decidir si queremos mostrar el contenido dentro de la aplicación o en un navegador web como veremos más adelante.

Construyendo Web Apps con WebView

Para agregar un WebView en nuestra aplicación Android debemos de realizar una serie de configuraciones que veremos a continuación:

Habilitar permiso a Internet en el AndroidManifest

Una de las cosas que primero debemos hacer es solicitar el permiso necesario en nuestro AndroidManifest para que la aplicación pueda conectarse a Internet y poder descargar el contenido HTML:

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

La forma moderna con Jetpack Compose

Con la llegada de Jetpack Compose, la forma de construir interfaces de usuario en Android ha cambiado a un enfoque declarativo. Para integrar un WebView en este nuevo mundo, utilizamos el composable AndroidView, que nos permite embeber vistas de Android tradicionales (basadas en XML) dentro de una UI de Compose.

app/src/main/AndroidManifest.xml

Habilitar permiso a Internet en el AndroidManifest

Al igual que en el método tradicional, es indispensable solicitar el permiso de INTERNET en el fichero AndroidManifest.xml:

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

Creando el Composable para el WebView

Ahora, creamos una función Composable que encapsula el WebView. Esto nos permite reutilizarlo fácilmente en cualquier parte de nuestra aplicación.

1. Configuración del Factory

Utilizamos AndroidView para inyectar la vista clásica de Android en el entorno de Compose. Especificamos que ocupe todo el tamaño de la pantalla mediante un Modifier.

JavaScript: Es fundamental habilitar JavaScript (settings.javaScriptEnabled = true) para que la página funcione correctamente si tiene elementos interactivos o diseños dependientes de scripts.

Carga de URL: Usamos la función loadUrl pasándole el parámetro que recibe nuestro 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. El bloque Update

El parámetro update es muy útil (aunque opcional). Si en tu aplicación la URL cambia dinámicamente, este bloque se encargará de actualizar el contenido automáticamente sin necesidad de recrear toda la vista.

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

Control de eventos con WebViewClient

El WebViewClient nos permite definir eventos y comportamientos específicos del cliente. Por ejemplo:

  • Navegación interna: Evitar que, al hacer clic en un enlace, el sistema abra el navegador externo del teléfono, manteniendo al usuario dentro de nuestra app.
  • Ciclo de carga: Ejecutar acciones cuando la página comienza a cargar o cuando termina (ideal para mostrar un loading o barra de progreso).
  • Gestión de errores: Manejar fallos de conexión o bloquear el acceso a URLs específicas mediante verificaciones personalizadas.

1. Evitar que los enlaces se abran en 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. Mostrar un "Cargando" (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. Manejar errores de conexión

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. Bloquear URLs específicas

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)
   }
}

⚙️ Configuraciones Avanzadas (Settings)

A través de settings, puedes personalizar la experiencia de navegación según tus necesidades:

  • Vista de escritorio: Forzar la versión "Desktop" del sitio.
  • Ajuste de pantalla: Escalar el contenido para que ocupe exactamente el ancho del móvil.
  • Control de Zoom: Habilitar o quitar los efectos de "pinza" para hacer zoom in/out, u ocultar los botones de control de zoom.
  • Caché y Almacenamiento: Habilitar el almacenamiento local en caché para mejorar la velocidad.
  • Seguridad: Evitar que scripts abran ventanas emergentes (pop-ups) de forma automática.

1. Optimización de Visualización y Zoom

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. Almacenamiento y Rendimiento (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. Seguridad y Privacidad

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. Acceso a Archivos y Cámara

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
}

Ejemplo de configuración “Todo Terreno”

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) }
)

Usando el WebView en tu Actividad  (Método Legacy)

Vamos a conocer la forma con XML que es la de legado. Llamamos a nuestro composable WebViewPage desde nuestra MainActivity o cualquier otra pantalla de nuestra aplicación.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            WebViewPage(url = "https://desarrollolibre.net")
        }
    }
}

Explicación del código:

- AndroidView: Es el composable clave que nos permite usar una Vista de Android tradicional dentro de Compose.

- factory: Este bloque se ejecuta una sola vez para crear la WebView. Aquí configuramos sus parámetros iniciales, como el layoutParams, el webViewClient y la habilitación de JavaScript.

- update: Este bloque se llama cada vez que el composable se recompone. Lo usamos para actualizar la vista, en este caso, para cargar una nueva URL si ha cambiado.

- WebViewClient: Al igual que en el enfoque tradicional, es crucial para manejar la navegación dentro del WebView y evitar que se abran navegadores externos.

Con estos pasos, ya tienes un WebView funcional en tu aplicación de Jetpack Compose, siguiendo las prácticas modernas de desarrollo de Android.

Cargando la página web en el WebView: Layout y Actividad

Una vez solicitado el permiso agregamos el tag WebView en el layout de la actividad de Android:

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

Finalmente podemos agregar la URL de la página web que deseemos mostrar a través del método loadUrl():

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

En nuestra aplicación agregaremos la siguiente URL referenciada en el código:

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

Manejando la navegación de la página

Ya en este punto podemos ejecutar nuestra aplicación Android; pero al ejecutar la misma veremos una pantalla como la siguiente:

Webview en un navegador

La idea de una crear una Web App con la clase WebView no es que simplemente abramos el contenido en alguno de los navegadores web que tengamos instalado en el dispositivo Android, si no que se ejecute dentro de nuestra aplicación; para esto veremos el siguiente bloque.

La clase WebViewClient

Como comentamos anteriormente; por defecto Android lanza una aplicación (como los navegadores web) que maneje la URL anteriormente establecida; pero podemos personalizar este comportamiento para que la maneje internamente nuestra aplicación; en otras palabras, que la página web se ejecute directamente en nuestra aplicación.

Sencillamente creamos una instancia de la clase WebViewClient y es lo mínimo que necesitamos para mostrar una página web dentro de nuestra aplicación:

webView.setWebViewClient(new WebViewClient());

Pero si deseamos más control podemos crear nuestra propia clase que extienda de WebViewClient y sobrescribir ciertos métodos:

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

Habilitando el JavaScript

El JavaScript se encuentra deshabilitado por defecto en el WebView, lo cual puede ser un problema según el tipo de página web que se desee cargar; es decir, si nuestra página a cargar a través del método loadUrl() necesita JavaScript; podemos habilitar el Internet fácilmente a través del método setJavaScriptEnabled(); veamos como usarlo:

Primero creamos un objeto WebSettings que permite realizar configuraciones variadas, aunque en nuestro caso, simplemente nos interesa habilitar el JavaScript:

WebSettings webSettings = myWebView.getSettings();

Ahora si podemos emplear el método setJavaScriptEnabled() pasando como parámetro el booleano true.

webSettings.setJavaScriptEnabled(true);

Al ejecutar el código:

Webview en la app

Siguiente paso, crea una notificación en Android.

Cómo insertar videos de YouTube en Android con Jetpack Compose. Aprende a configurar dependencias, gestionar el ciclo de vida con DisposableEffect y usar AndroidView.

Acepto recibir anuncios de interes sobre este Blog.

Andrés Cruz

EN In english