WebView con AndroidStudio. De WEBAPP en PHP a aplicación Android.

WebView con AndroidStudio

WebView con AndroidStudio. De WEBAPP en PHP a aplicación Android. Cómo convertir nuestra aplicación WEB PHP  en una app Android que soporte COOKIES, SUBIDAS y DESCARGAS DE ARCHIVOS.

Despues de dedicarle decenas de horas a programar nuestra aplicación Web, ahora nos encontramos con la ilusión de poder convertirla en una aplicación Android e incluso verla expuesta en GooglePlay.

Supongo que igual que yo, habréis leido bastante sobre los WebViews para Android o habéis intentado hacerlo con App Inventor. Al final me he decantado por utilizar un Webview con Android Studio.

En este post os compartiré el código necesario para, una vez habéis terminado con vuestra WebApp, podáis convertirla en App de Android.

Además , tiene las siguientes características:

Permite el uso de Cookies. Si nuestra aplicación requiere registro de usuarios y cookies para el Login, éstas cookies quedarán guardadas para la próxima vez que inicies la app.

Permite la descarga de archivos.  Las imágenes y estilos de nuestra app quedarán guardadas en el caché de nuestro teléfono.

Permite la subida de archivos. Soporta la subida de archivos a nuestro servidor desde formularios HTML.

Bien, empecemos:

Creando nuestro proyecto.

Una vez hayamos instalado Android Studio, procederemos a crear nuestro primer proyecto.

Si no tienes un proyecto abierto, Android Studio muestra la pantalla de bienvenida, donde puedes hacer clic en Start a new Android Studio project para crear un nuevo proyecto.

Si tienes un proyecto abierto, para comenzar a crear un nuevo proyecto selecciona File > New> New Project en el menú principal.

Deberías ver el asistente Create New Project, que te permite elegir el tipo de proyecto que deseas crear y se completa con código y recursos para comenzar. Esta página sirve como guía para la creación de un nuevo proyecto desde el asistente Create New Project.

En la pantalla Choose your project que aparece, puedes seleccionar el tipo de proyecto que deseas crear entre las categorías de factores de forma del dispositivo, que se muestran como pestañas cerca de la parte superior del asistente.

nuevo proyecto con AndroidStudio

IMPORTANTE: En este caso hay que seleccionar EMPTY ACTIVITY

Haz tu selección y, luego, haz clic en Next.

nuevo proyecto con AndroidStudio

IMPORTANTE: Para este ejemplo hay que introducir los siguientes datos

Name: myapp / Languaje: Java.

El resto, lo dejamos tal cual y esperamos a que se genere el proyecto. Tarda un poco, paciencia.

Primer archivo: AndroidManifest.xml

Copia el siguiente código sustituyendo el actual

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/myapp_icon"
        android:label="My App"
        android:roundIcon="@mipmap/myapp_icon_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.NoActionBar"
        android:versionCode="4"
        android:versionName="1.0.3">

        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

PERMISOS

Verás que en las primeras filas ya aparecen los permisos necesarios de tu app. En este caso necesitamos permiso para conectarse a INTERNET, para DESCARGAR archivos y para SUBIR archivos. Hay muchos más que puedes añadir si tu aplicación los necesita.

Después se pide la información básica de la aplicación: Nombre, Iconos, Versión….

Deberás crear tus propios iconos para la App. Te explico cómo:

ICONOS

Hay que  iniciar Image Asset Studio, sigue estos pasos:

Haz clic con el botón secundario en la carpeta res y selecciona New > Image Asset.

 

nueva imagen con AndroidStudio

Deberás crear 2 imágenes para tu app,

android:icon="@mipmap/myapp_icon"
android:roundIcon="@mipmap/myapp_icon_round"

y en la parte de la propia actividad <activity> hemos puesto android:screenOrientation=»portrait» para que la aplicación sólo se vea en vertical. Puedes eliminarlo o modificarlo en función de tus necesidades.

Y con esto damos por completado el archivo AndroidManifest.xml

 

Segundo archivo: activity_main.xml

En este caso, al ser algo tan simple para la actividad, simplemente tiene 2 instrucciones:

Decirle qué WebView tiene que mostrar, en este caso le hemos asignado como id en nombre «web_view» y especificamos el  android:layout_height  android:layout_width como «match_parent» para que se ajuste al total de las pantallas.

El código es el siguiente:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" android:layout_width="match_parent"
	android:layout_height="match_parent">

	<WebView
		android:id="@+id/web_view"
		android:layout_width="match_parent"
		android:layout_height="match_parent" />
	</LinearLayout>

 

Tercer archivo: MainActivity.java

Y aquí llegamos al último de nuestros archivos necesarios, el archivo .java.

Aquí es donde se añaden todas las intrucciones para el correcto funcionamiento de nuestra app. No voy a extenderme mucho en explicaciones ya que, si os soy sincero, aún estoy familiarizándome con estas lineas de código y todo lo que hacen.

Vereis que lo primero que hace es cargar todas las librerias que necesita. Pero intentaré aclarar algunas cosas.

  • LINEA 92,  sirve para ocultar el STATUS BAR mientras se ejecuta nuestra aplicación.
  • LINEA 118webView.loadUrl («http://mi_app.com/»);  aqui es donde ponemos la dirección de nuestra web . Recordad poner la dirección absoluta.
  • LINEA 133,  funcion para permitir descargas de archivos.
  • LINEA 172,  las siguientes lineas hacen que se permita la subida de archivos desde la app a nuestro servidor a través de un boton <input type=»file»> de nuestra webapp. En este caso sólo permite subir imágenes. Puedes modificarlo en la linea 200.
  • LINEA 200, especificamos el tipo de archivos permitidos en la subida, pueden ser imágenes, pdf…
  • LINEA 233,  permite el BACK desde el botón del móvil en caso de poder volver atrás. En caso contrario, cerrará la aplicación.

Y aquí os dejo el código completo.

package com.example.myapp;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.webkit.CookieManager;
import android.webkit.DownloadListener;
import android.webkit.URLUtil;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity{
    WebView webView;
    private static final String TAG = MainActivity.class.getSimpleName();
    private String mCM;
    private ValueCallback mUM;
    private ValueCallback<Uri[]> mUMA;
    private final static int FCR=1;

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent){
        super.onActivityResult(requestCode, resultCode, intent);
        if(Build.VERSION.SDK_INT >= 21){
            Uri[] results = null;
            //Check if response is positive
            if(resultCode== Activity.RESULT_OK){
                if(requestCode == FCR){
                    if(null == mUMA){
                        return;
                    }
                    if(intent == null || intent.getData() == null){
                        //Capture Photo if no image available
                        if(mCM != null){
                            results = new Uri[]{Uri.parse(mCM)};
                        }
                    }else{
                        String dataString = intent.getDataString();
                        if(dataString != null){
                            results = new Uri[]{Uri.parse(dataString)};
                        }
                    }
                }
            }
            mUMA.onReceiveValue(results);
            mUMA = null;
        }else{
            if(requestCode == FCR){
                if(null == mUM) return;
                Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();
                mUM.onReceiveValue(result);
                mUM = null;
            }
        }
    }

    @SuppressLint({"SetJavaScriptEnabled", "WrongViewCast"})
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(com.example.myapp.R.layout.activity_main);


//-------------ocultar STATUS BAR---------------------------
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
//------------------------------------------------------


        if(Build.VERSION.SDK_INT >=23 && (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 1);
        }
//Runtime External storage permission for saving download files
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                == PackageManager.PERMISSION_DENIED) {
                Log.d("permission", "permission denied to WRITE_EXTERNAL_STORAGE - requesting it");
            String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
            requestPermissions(permissions, 1);
        }
    }


    webView = findViewById(R.id.web_view);
    webView.setWebViewClient(new WebViewClient());
    webView.getSettings().setLoadsImagesAutomatically(true);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
    webView.setWebViewClient(new Callback());


    webView.loadUrl("https://la_web_de_mi_app.com/index.html");

        //--------------UPLOAD FILES-------------------
    webView.getSettings().setLoadsImagesAutomatically(true);
    webView.getSettings().setAllowFileAccess(true);
    webView.getSettings().setAllowContentAccess(true);
    webView.getSettings().setPluginState(WebSettings.PluginState.ON);
        //-------------------------------------------






        //handle downloading
    webView.setDownloadListener(new DownloadListener()
    {
        @Override
        public void onDownloadStart(String url, String userAgent,
            String contentDisposition, String mimeType,
            long contentLength) {
            DownloadManager.Request request = new DownloadManager.Request(
                Uri.parse(url));
            request.setMimeType(mimeType);
            String cookies = CookieManager.getInstance().getCookie(url);
            request.addRequestHeader("cookie", cookies);
            request.addRequestHeader("User-Agent", userAgent);
            request.setDescription("Descargando archivo");
            request.setTitle(URLUtil.guessFileName(url, contentDisposition, mimeType));
            request.allowScanningByMediaScanner();
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            request.setDestinationInExternalPublicDir(
                Environment.DIRECTORY_DOWNLOADS, URLUtil.guessFileName(
                    url, contentDisposition, mimeType));
            DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
            dm.enqueue(request);
            Toast.makeText(getApplicationContext(), "Descargando archivo", Toast.LENGTH_LONG).show();
        }});
//---------------------------------------------------------------------------------------
    webView = (WebView) findViewById(R.id.web_view);
    assert webView != null;
    WebSettings webSettings = webView.getSettings();
    webSettings.setJavaScriptEnabled(true);
    webSettings.setAllowFileAccess(true);

    if(Build.VERSION.SDK_INT >= 21){
        webSettings.setMixedContentMode(0);
        webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    }else if(Build.VERSION.SDK_INT >= 19){
        webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    }else {
        webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }

    webView.setWebChromeClient(new WebChromeClient(){

            //For Android 5.0+
        public boolean onShowFileChooser(
            WebView webView, ValueCallback<Uri[]> filePathCallback,
            FileChooserParams fileChooserParams){
            if(mUMA != null){
                mUMA.onReceiveValue(null);
            }
            mUMA = filePathCallback;
            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            if(takePictureIntent.resolveActivity(MainActivity.this.getPackageManager()) != null){
                File photoFile = null;
                try{
                    photoFile = createImageFile();
                    takePictureIntent.putExtra("PhotoPath", mCM);
                }catch(IOException ex){
                    Log.e(TAG, "Image file creation failed", ex);
                }
                if(photoFile != null){
                    mCM = "file:" + photoFile.getAbsolutePath();
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
                }else{
                    takePictureIntent = null;
                }
            }
            Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
            contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);


// En la siguiente linea especificamos el tipo de archivos que permitimos subir desde la aplicación. 
            contentSelectionIntent.setType("image/*");
// Si deseamos permitir subir otro archivos, por ejemplo PDF, modificaremos la linea y la dejaremos de la siguiente manera:
//          contentSelectionIntent.setType("application/pdf, image/*");

            Intent[] intentArray;
            if(takePictureIntent != null){
                intentArray = new Intent[]{takePictureIntent};
            }else{
                intentArray = new Intent[0];
            }

            Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
            chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
            chooserIntent.putExtra(Intent.EXTRA_TITLE, "Selección de Imagen");
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
            startActivityForResult(chooserIntent, FCR);
            return true;
        }
    });
}
public class Callback extends WebViewClient{
    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl){
        Toast.makeText(getApplicationContext(), "Descargando archivo", Toast.LENGTH_SHORT).show();
    }
}
    // Create an image file
private File createImageFile() throws IOException{
    @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "img_"+timeStamp+"_";
    File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
    return File.createTempFile(imageFileName,".jpg",storageDir);
}
@Override


//------------PERMITE BACK CON BOTON DEL MOVIL O NAVEGADOR----------------------//
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event){
    if(event.getAction() == KeyEvent.ACTION_DOWN){
        switch(keyCode){
            case KeyEvent.KEYCODE_BACK:
            if(webView.canGoBack()){
                webView.goBack();
            }else{
                finish();
            }
            return true;
        }
    }
    return super.onKeyDown(keyCode, event);
}

@Override
public void onConfigurationChanged(Configuration newConfig){
    super.onConfigurationChanged(newConfig);
}
}

Yo he usado estos códigos en 3 aplicaciones. Me han funcionado, y siguen haciéndolo perfectamente. En cualquier caso os recomiendo ver videos y leer mucho para aprender más y hacerlo mejor.

Mientras tanto, yo seguiré publicando ayudas para todos aquellos que empezáis. Como yo.

Espero que os sirva y no dudeis en comentar. Hasta pronto!

 

Compartir esta entrada.

15 thoughts on “WebView con AndroidStudio. De WEBAPP en PHP a aplicación Android.

  1. No hacer caso comentario de abajo, la pregunta de verdad es por que al descargar un archivo se descargar 6 o 7 hasta que se le de cancelar, con solo darle una vez?

    1. Pues no veo donde puede estar el problema.
      En mi app, usando este mismo codigo, me descarga el archivo una sola vez.
      Has mirado si ejecutando tu codigo directamente en Chrome o Firefox tealiza correctamente la descarga?

      1. Si, copee y pegue este código tal cual, otro detalle es que solo deja descargar imágenes, hay que configurarle algo para que deje descargar cualquier tipo de archivo?

  2. Asi es amigo. Probé tu código y si me permite cargar imágenes, funciona correctamente, solo que archivos PDF o word salen deshabilitas. Porfavor quisiera saber cómo poder también cargar esos archivos. Mencionaste que en la línea 200 se puede hacer. Gracias de antemano por tu gran ayuda

    1. Hola Jaime. Como ya te dije en el mensaje anterior, la limitacion del tipo de archivo que puedes subir viene determinada por el codigo HTML de tu formulario.
      No tiene nada que ver el codigo de este post.
      Te será muy fácil encontrar en Google información al respecto. Has de usar el atributo «accept» en tu input.
      Por ejemplo, este input permite seleccionar imagenes y archivos .pdf.

      input type=»file» accept=»image/*,.pdf»

      Además, como muy amablemente con comneta un usuario ANONIMO, deberás modificar la linea 200 del código y añadir esta modificación:
      contentSelectionIntent.setType( “application/pdf, image/*”);

      Suerte!!

  3. Buen día estimado amigo. Primero felicitarte me ayudado tu código. Quisiera porfavor me ayudes a que pueda aceptar cargar en los input también archivos. Mencionaste la línea 200. Cual seria el proceso para poder permitir también archivos.

    1. Hola Jaime. Un webview no deja de ser mostrar en la app una pagina web o una aplicacion.
      Para poder subir archivos de todo tipo has de ponerlo en el código PHP de tu aplicacion. Puedes probar tu formulario entrando directamente en la web en lugar de desde la app.
      Espero que te funcione.
      Saludos.

      1. Asi es amigo. Probé tu código y si me permite cargar imágenes, funciona correctamente, solo que archivos PDF o word salen deshabilitas. Porfavor quisiera saber cómo poder también cargar esos archivos. Mencionaste que en la línea 200 se puede hacer. Gracias de antemano por tu gran ayuda

  4. Buenas, antetodo excelente trabajo, me ha servido de mucho, pero me he quedado atascado puesto que no soy capaz de hacer para dar permiso a la geolocalización, es una web que aparece un mapa y cuando carga te pide permiso, pero en la app no lo hace…. ¿Me podrias aconsejar? Gracias de antemano.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

CAPTCHA ImageChange Image