Crear un plugin para Android en PhoneGap

7
20368

Crear un plugin para Android en PhoneGap

0. Índice de
contenidos.

1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 17″ (2,6 Ghz Intel Core i7, 8 GB DDR3)
  • Sistema Operativo: Mac OS X Snow Leopard 10.6.4
  • Apache Cordova 3.4.0-0.1.0

2. Introducción

Antes de seguir con este tutorial se aconseja haber completado este
otro
que
nos muestra como empezar a dar los primeros pasos y este otro
que nos enseña como utilizar los plugin en PhoneGap/Cordova.

Además si todavía te haces un lío entre que es PhoneGap y que es Apache Cordova te recomiendo leer
está noticia al respecto.

Bien lo que vamos a ver en este tutorial es cómo crear un plugin que podamos reutilizar en cualquier de nuestros desarrollo con PhoneGap/Cordova. Los plugins son la mejor
forma de extender funcionalidad en PhoneGap permitiendo la ejecución de código nativo invocado desde Javascript.

Para este tutorial nos vamos a centrar en cómo crear un plugin para dispositivos Android que permita el envío de emails desde la aplicación sin necesidad de tener que
utilizar el gestor de correo de Android y sin que el usuario tenga que dar explícitamente a «Enviar».

3. Vamos al lío

Lo primero que vamos a hacer es crear nuestro plugin. Para ello vamos a crear una estructura de carpetas que almacenen todos los componentes
necesarios para la implementación del plugin. Por tannto en cualquier parte que decidamos de nuestro sistema de ficheros vamos a crear la carpeta
«send-mail-cordova-plugin».

La forma en la que PhoneGap se comunica con la parte nativa es a través de esta llamada javascript:

        cordova.exec(successCallback, failureCallback, class, method, [arguments]);
    

Estos son los argumentos:

  • successCallback: será la función que se quiera ejecutar cuando el resultado de la invocación sea satisfactorio.
  • failureCallback: será la función a ejecutar cuando el resultado de la invocación no sea satisfactorio.
  • class: será el nombre de la clase de nuestro código nativo, sin tener en cuenta el nombre del paquete.
  • method: será el nombre de la acción que se va a tener en cuenta en el método «execute» de la clase anterior que queremos invocar.
  • [arguments]]: será un array, generalmente en formato JSON, donde se le pasan todos los parámtros de entrada al método invocado.

Por tanto lo primero que vamos a hacer es crear nuestro fichero js con la llamada al código nativo. Para ello dentro de la carpeta
«send-mail-cordova-plugin» creamos otra carpeta llamada «www» y dentro creamos el fichero «sendmail.js» con el siguiente contenido:

      var sendmail = {
    
        send: function(successCallback, errorCallback, subject, body, sender, password, recipients){
          cordova.exec(successCallback,
              errorCallback,
              "SendMail",
              "send",
              [{
                "subject":subject, 
                  "body":body,
                  "sender":sender,
                  "password":password, 
                  "recipients":recipients
              }]
          );
          
        }
      }

      module.exports = sendmail;

    

En la llamada estamos indicando que vamos a tener una clase llamada «SendMail» con la acción «send» que será el encargado de recoger
los argumentos y realizar el envio del email. Además fijaos en la forma de pasar los argumentos para que se recojan en formato JSON.

Por tanto el siguiente paso lógico es crear la clase especificada. Para ello dentro de la carpeta «send-mail-cordova-plugin» vamos a crear
otra llamada «src» y dentro de esta otra llamada «android» para distinguir las plataformas por si queremos extender la funcionalidad de este
plugin a otras plataformas soportadas como IOS o Windows Phone.

Dentro de la carpeta «android» vamos a crear el fichero SendMail.java. En este punto os podéis apoyar en vuestro IDE favorito para implementar
el código. Para la implementación del método me he apoyado en un código que me pasó nuestra genial mañica con su no menos genial twitter
@Sara_Subidon y el no menos único y genial @ifdezmolina 😉

      
      package com.autentia.plugin.sendmail;

      import org.apache.cordova.CallbackContext;
      import org.apache.cordova.CordovaPlugin;
      import org.json.JSONArray;
      import org.json.JSONException;
      import org.json.JSONObject;

      public class SendMail extends CordovaPlugin {

        public static final String ACTION_SEND = "send";

        public boolean execute(String action, JSONArray jsonArgs,
            CallbackContext callbackContext) throws JSONException {
          try {
            if (ACTION_SEND.equals(action)) {
              JSONObject args = jsonArgs.getJSONObject(0);
              String subject = args.getString("subject");
              String body = args.getString("body");
              String sender = args.getString("sender");
              String password = args.getString("password");
              String recipients = args.getString("recipients");

              GMailSender gmailSender = new GMailSender(
                  sender, password);

              gmailSender.sendMail(subject, body, sender, recipients);

            }
            callbackContext.success();
            return true;
          } catch (Exception e) {
            callbackContext.error(e.getMessage());
            return false;
          }
        }
      }
    

Como vemos no es más que una clase Java que extiende de la clase abstracta CordovaPlugin que hace que tenga que implementar el método
«execute». En este método distinguimos por el nombre de la acción que le pasamos, dependiendo del resultado de la acción devolvemos true
o false y llamaremos al correspondiente callback. También tenemos que fijarnos en cómo recupera los argumentos de entrada en JSON
a sus respectivas variables String.

Este código tiene dependencias con otras clases y librerías; por lo tanto tenemos que incluirlas en nuestro plugin. Para incluir las
clases vamos a crear a la misma altura que «SendMail.java» el fichero «GMailSender.java» el cual contiene el siguiente código:

      package com.autentia.plugin.sendmail;

      import java.io.ByteArrayInputStream;
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.OutputStream;
      import java.security.Security;
      import java.util.Properties;

      import javax.activation.DataHandler;
      import javax.activation.DataSource;
      import javax.mail.Message;
      import javax.mail.PasswordAuthentication;
      import javax.mail.Session;
      import javax.mail.Transport;
      import javax.mail.internet.InternetAddress;
      import javax.mail.internet.MimeMessage;

      public class GMailSender extends javax.mail.Authenticator {   
          private String mailhost = "smtp.gmail.com";   
          private String user;   
          private String password;   
          private Session session;   

          static {   
              Security.addProvider(new JSSEProvider());   
          }  

          public GMailSender(String user, String password) {   
              this.user = user;   
              this.password = password;   

              Properties props = new Properties();   
              props.setProperty("mail.transport.protocol", "smtp");   
              props.setProperty("mail.host", mailhost);   
              props.put("mail.smtp.auth", "true");   
              props.put("mail.smtp.port", "465");   
              props.put("mail.smtp.socketFactory.port", "465");   
              props.put("mail.smtp.socketFactory.class",   
                      "javax.net.ssl.SSLSocketFactory");   
              props.put("mail.smtp.socketFactory.fallback", "false");   
              props.setProperty("mail.smtp.quitwait", "false");   

              session = Session.getDefaultInstance(props, this);   
          }   

          protected PasswordAuthentication getPasswordAuthentication() {   
              return new PasswordAuthentication(user, password);   
          }   

          public synchronized void sendMail(String subject, String body, String sender, String recipients) throws Exception {   
              
              MimeMessage message = new MimeMessage(session);   
              DataHandler handler = new DataHandler(new ByteArrayDataSource(body.getBytes(), "text/plain"));   
              message.setSender(new InternetAddress(sender));   
              message.setSubject(subject);   
              message.setDataHandler(handler);   
              if (recipients.indexOf(',') > 0)   
                  message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipients));   
              else  
                  message.setRecipient(Message.RecipientType.TO, new InternetAddress(recipients));   
              Transport.send(message);   
              
          }   

          public class ByteArrayDataSource implements DataSource {   
              private byte[] data;   
              private String type;   

              public ByteArrayDataSource(byte[] data, String type) {   
                  super();   
                  this.data = data;   
                  this.type = type;   
              }   

              public ByteArrayDataSource(byte[] data) {   
                  super();   
                  this.data = data;   
              }   

              public void setType(String type) {   
                  this.type = type;   
              }   

              public String getContentType() {   
                  if (type == null)   
                      return "application/octet-stream";   
                  else  
                      return type;   
              }   

              public InputStream getInputStream() throws IOException {   
                  return new ByteArrayInputStream(data);   
              }   

              public String getName() {   
                  return "ByteArrayDataSource";   
              }   

              public OutputStream getOutputStream() throws IOException {   
                  throw new IOException("Not Supported");   
              }   
          }   
      } 

    

Esta clase tiene otra depencia con la clase «JSSEProvider». Por tanto creamos un tercer fichero a la altura de los otros dos llamado
«JSSEProvider.java» con el siguiente contenido:

    package com.autentia.plugin.sendmail;

    import java.security.AccessController;
    import java.security.Provider;

    public final class JSSEProvider extends Provider {

      private static final long serialVersionUID = 1L;

      public JSSEProvider() {
            super("HarmonyJSSE", 1.0, "Harmony JSSE Provider");
            AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {
                public Void run() {
                    put("SSLContext.TLS",
                            "org.apache.harmony.xnet.provider.jsse.SSLContextImpl");
                    put("Alg.Alias.SSLContext.TLSv1", "TLS");
                    put("KeyManagerFactory.X509",
                            "org.apache.harmony.xnet.provider.jsse.KeyManagerFactoryImpl");
                    put("TrustManagerFactory.X509",
                            "org.apache.harmony.xnet.provider.jsse.TrustManagerFactoryImpl");
                    return null;
                }
            });
        }
    }

  

Lo importante de este ejemplo no es tanto la implementación de la solución como que se vea de que forma podemos
incluir nuevas clases y líbrerías de terceros para el desarrollo de nuestros plugins. En este caso la implementación
depende de cuatro líbrerías: activation.jar, additional.jar, httpmime-4.0.jar y mail.jar las cuales vamos a incluir en
dentro de una carpeta «libs» en el mismo directorio que las clases anteriores.

En este punto ya tenemos todo el código de nuestro plugin, pero ahora viene la parte importante de cómo indicar al
proyecto que vaya hacer uso de nuestro plugin la forma en la que tiene que incluir estos fuentes.

PhoneGap/Cordova lo consigue a través de la definición del fichero plugin.xml. Este fichero contiene la información
de nuestro plugin como el nombre, las plataformas que soporta y sobre todo la información de distribución de los fuentes
del plugin el proyecto que lo vaya a utilizar.

En nuestro caso vamos a crear el fichero plugin.xml el directorio raíz del plugin con el siguiente contenido:

    <?xml version="1.0" encoding="UTF-8"?>
 
    <plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
               id="com.autentia.plugin.sendmail"
          version="0.1.0">

        <name>PluginSendMail</name>
        <description>Send Mail Plugin</description>
        <license>MIT</license>
        <keywords>phonegap,mail</keywords>
     
     
        <js-module src="www/sendmail.js" name="SendMail">
            <clobbers target="sendmail" />
        </js-module>
     
        <!-- android -->
        <platform name="android">
            <config-file target="res/xml/config.xml" parent="/*">
                <feature name="SendMail">
                    <param name="android-package" value="com.autentia.plugin.sendmail.SendMail"/>
                </feature>
            </config-file>
            
            <source-file src="src/android/libs/activation.jar" target-dir="libs" framework="true"/>     
            <source-file src="src/android/libs/additionnal.jar" target-dir="libs" framework="true"/> 
            <source-file src="src/android/libs/httpmime-4.0.jar" target-dir="libs" framework="true"/> 
            <source-file src="src/android/libs/mail.jar" target-dir="libs" framework="true"/> 
            <source-file src="src/android/JSSEProvider.java" target-dir="src/com/autentia/plugin/sendmail" /> 
            <source-file src="src/android/GMailSender.java" target-dir="src/com/autentia/plugin/sendmail" /> 
            <source-file src="src/android/SendMail.java" target-dir="src/com/autentia/plugin/sendmail" />
         </platform>          
    </plugin>

  

La parte más importante de esta definición son los «source-file» donde indicamos donde se tienen que copiar los fuentes
dentro del proyecto que hace uso del plugin y la definición de «js-module» donde indicamos el fichero javascript que va a actuar
de puente entre la aplicación web y el mundo nativo y nos permite no tener que incluir el enlace «script» explícitamente dado que está
etiqueta hace que el código del script se envuelva automáticamente en un «closure» con los scope de «module», «exports» y «require». Además
la etiqueta «clobbers» con el atributo «target» indica el nombre que vamos a utilizar para la invocación de los métodos definidos.

En caso de querer extender esta funcionalidad a otras plataformas tendríamos que hacer la definición de fuentes de forma similar.

En este punto ya tenemos el código de nuestro plugin listo para ser probado en cualquier proyecto Cordova/PhoneGap.

Para hacer uso de nuestro plugin vamos a crear un nuevo proyecto Cordova/PhoneGap.

    cordova create TestPlugin com.autentia.TestPlugin
  

Esto creará la estructura por defecto de un proyecto Cordova. Entrando en la carpeta «TestPlugin» podemos añadir nuestro plugin
ejecutando:

    cordova plugin add PATH_RAIZ_PROYECTO_PLUGIN
  

En nuestro caso:

    cordova plugin add /Users/xxxx/Proyectos/send-mail-cordova-plugin
  

También indicamos que el proyecto queremos que funcione en Android, ejecutando:

    cordova platform add android
  

Ahora para probar el plugin vamos primero a editar el fichero «www/index.html» para añadir un div que permita mostrar el estado
de la invocación del método. Quedando el código de esta forma:

      <html>
          <head>
              <meta charset="utf-8" />
              <meta name="format-detection" content="telephone=no" />
              <!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
              <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
              <link rel="stylesheet" type="text/css" href="css/index.css" />
              <title>Hello World</title>
          </head>
          <body>
              <div class="app">
                  <h1>Apache Cordova</h1>
                  <div id="deviceready" class="blink">
                      <p class="event listening">Connecting to Device</p>
                      <p class="event received">Device is Ready</p>
                      <div id="estado">ESTADO</div>
                  </div>
              </div>
              
              <script type="text/javascript" src="cordova.js"></script>
              <script type="text/javascript" src="js/index.js"></script>
              <script type="text/javascript">
                  app.initialize();
              </script>
          </body>
      </html>

  

Ahora añadimos el código en el fichero «www/index.js» que hace la invocación al método teniendo en cuenta que le tendréis que
pasar valores válidos de vuestra cuenta de GMail. El código de este fichero quedaría de la siguiente forma:

      var app = {
        // Application Constructor
        initialize: function() {
            this.bindEvents();
        },
        // Bind Event Listeners
        //
        // Bind any events that are required on startup. Common events are:
        // 'load', 'deviceready', 'offline', and 'online'.
        bindEvents: function() {
            document.addEventListener('deviceready', this.onDeviceReady, false);
        },
        // deviceready Event Handler
        //
        // The scope of 'this' is the event. In order to call the 'receivedEvent'
        // function, we must explicity call 'app.receivedEvent(...);'
        onDeviceReady: function() {
            app.receivedEvent('deviceready');
            sendmail.send(app.sendMailSuccess, app.sendMailError,
                    'Correo enviado con Cordova',
                    'Este es un ejemplo de correo enviado con Cordova', 
                    'tucorreo@gmail.com', 'supercontraseña',
                    'aquien@gmail.com');
        },
        sendMailSuccess : function() {
            var estado = document.getElementById('estado');
            estado.innerHTML = '

Correo enviado

'; }, sendMailError : function(error) { var estado = document.getElementById('estado'); estado.innerHTML = '

Correo NO enviado:' + error + '

'; }, // Update DOM on a Received Event receivedEvent: function(id) { var parentElement = document.getElementById(id); var listeningElement = parentElement.querySelector('.listening'); var receivedElement = parentElement.querySelector('.received'); listeningElement.setAttribute('style', 'display:none;'); receivedElement.setAttribute('style', 'display:block;'); console.log('Received Event: ' + id); } };

Dentro de la función «onDeviceReady» realizamos la llamada a nuestra función. En caso de que la invocación sea errónea se
ejecutará el método «sendMailError» mostrando en pantalla el error que se ha producido. Si la invocación se hace correctamente el
destinatario recibirá el email y en nuestra aplicación se mostrará el texto «Correo enviado»

Para distribuir nuestro plugin simplemente subimos los fuentes a una cuenta de GitHub. Si queréis probarlo en vuestro proyecto
Cordova/PhoneGap solo tendréis que ejecutar:

  cordova plugin add https://github.com/raguilera82/send-mail-cordova-plugin.git

4. Conclusiones

Como véis no es complicado hacer nuestras propias extensiones de funcionalidad de Cordova/PhoneGap.

Cualquier duda o sugerencia en la zona de comentarios.

Saludos.

7 COMENTARIOS

  1. muy buen tutorial, solo tenia una consulta es que nesecito guardar los tiempos de llamada (no las llamadas) y loso numeros a los que se llamo, pero no me funciona, ( el problema es que mi conocimiento de java es casi nulo, por ello no logro saber como tengo que hacer el archivo .java) alguna sugerencia donde pueda aprender a escribir ese archivo?, ya he h=buscado varias fuentes pero no he tenido exito agradeceria sus respuestas.

  2. Una consulta, se puede hacer un plugin para conexión a bd? En este caso.realizaría una conexión a sql en java nativo para luego poder sincronizarlo con el sqlite de phonegap. Es posible??

  3. Hola tengo una pregunta…
    Es posible tomar una aplicación que fue desarrollada en IOS Nativo y crear un plugin para que funcione multiplataforma ?

  4. Hello,

    I implemented the email lib using phonegap build…when i try to run my app it is working fine but mail is not going and it is not giving any error also.

    My index.html code:

    Hello World

    function deviceType() {
    var deviceType = (navigator.userAgent.match(/iPhone/i)) == «iPhone» ? «iPhone» : (navigator.userAgent.match(/Android/i)) == «Android» ? «Android»: «null»;
    alert(deviceType);
    }

    WaterSenzTM

    Connecting to Device
    Device is Ready
    Dashboard

    app.initialize();
    deviceType();

    2: index.js file:

    var app = {
    // Application Constructor
    initialize: function() {
    this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // ‘load’, ‘deviceready’, ‘offline’, and ‘online’.
    bindEvents: function() {
    document.addEventListener(‘deviceready’, this.onDeviceReady, false);
    },
    // deviceready Event Handler
    //
    // The scope of ‘this’ is the event. In order to call the ‘receivedEvent’
    // function, we must explicity call ‘app.receivedEvent(…);’
    onDeviceReady: function() {
    app.receivedEvent(‘deviceready’);
    sendmail.send(app.sendMailSuccess, app.sendMailError,
    ‘(hi)’,
    ‘welcome’,
    ‘****@gmail.com’, ‘*********’,
    ‘******@gmail.com’);
    },
    sendMailSuccess : function() {
    console.log(‘Email send’);
    alert(‘Email send’);
    },
    sendMailError : function(error) {
    console.log(‘Error: ‘ + error);
    alert(‘Error: ‘ + error);
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
    var parentElement = document.getElementById(id);
    var listeningElement = parentElement.querySelector(‘.listening’);
    var receivedElement = parentElement.querySelector(‘.received’);

    listeningElement.setAttribute(‘style’, ‘display:none;’);
    receivedElement.setAttribute(‘style’, ‘display:block;’);

    console.log(‘Received Event: ‘ + id);
    alert(‘Received Event: ‘ + id);
    }
    };

    can any one help me to resolve this.

    Thanks.

  5. Hello,
    I am trying to develop a phonegap app.In this process i have to send a email so i used the plugin while am running the app i got an error:class not found.
    Can anyone help me how to solve this issue.
    Thank you.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad