jueves, 5 de junio de 2014

Plugin device-orientation (brújula)

El nombre lo dice casi todo. Este plugin Phonegap/Cordova sirve para saber hacia dónde apunta el dispositivo, accediendo a la brújula. La brújula del dispositivo obtiene la dirección a la que apunta el mismo, normalmente, desde la parte superior, y está expresada en grados, desde 0 a 359'99 donde el 0 es el norte.

Plataformas soportadas

Este plugin está soportado por todos los sistemas más usados, por lo que implementarlo nos garantiza prácticamente 0 problemas de compatibilidad. Aquí el listado de los mismos:

  • Amazon Fire OS
  • Android
  • BlackBerry 10
  • Firefox OS
  • iOS
  • Tizen
  • Windows Phone 7 y 8
  • Windows 8

Instalación

Basta con escribir en consola, dentro de la carpeta del proyecto lo siguiente:
cordova plugin add org.apache.cordova.device-orientation

Y ya podremos hacer uso de los métodos de la API device-orientation.

Métodos

Son los siguientes, y podemos observar bastantes similitudes en su comportamiento respecto a los métodos de otras API como la del acelerómetro o la geolocalización.

  • navigator.compass.getCurrentHeading(onSuccess, onError);
  • navigator.compass.watchHeading(onSuccess, onError, [compassOptions]);
  • navigator.compass.clearWatch(watchID);
La explicación del funcionamiento de estos métodos es similar a la de los métodos análogos de otros plugins ya explicados, con la diferencia de que a las funciones callback onSuccess y onError se les envían otros objetos como parámetros. El objeto enviado a la función onSuccess es el llamado CompassHeading. Y a la función onError se le pasa el objeto CompassError. Por ejemplo, en el caso del acelerómetro, recordemos que a la función onSuccess(o como quisiéramos llamarla) se le pasaba el objeto Acceleration. Por lo demás, el funcionamiento es muy parecido. Con getCurrentHeading obtenemos la dirección a la que apunta el dispositivo en un momento determinado, con watchHeading realizamos un muestreo que referenciamos en una variable(en explicaciones anteriores la habíamos llamado watchID), y clearWatch sirve para detener ese muestreo.
También disponemos de un objeto de opciones, que podemos pasar de forma optativa, y contendrá la configuración con la que realizaremos el muestreo. Los valores que puede contener el objeto compassOptions son frequency y filter. Frequency determina el valor en milisegundos en el que haremos el muestreo de datos. Si además incluimos la opción filter, frequency es ignorado. Filter indica el valor mínimo en grados requerido para realizar una nueva llamada a la función onSuccess. No recomiendo incluir filter, porque no está soportado por muchos de los principales sistemas(Android, Windows Phone 7 y 8, entre otros).

Objetos

Como hemos mencionado antes, el objeto CompassHeading es pasado como parámetro a la función onSuccess cuando se obtienen datos de forma satisfactoria. Podemos acceder a sus siguientes propiedades:

  • magneticHeading: Valor numérico expresado en grados, que varía entre 0 y 359'99 y que indica hacia adónde apunta el dispositivo en el momento que se obtiene.
  • trueHeading: Igual que magneticHeading, pero relativo al polo Norte geográfico. Un valor negativo indica que no se puede determinar este valor.
  • headingAccuracy: Desviación en grados que puede haber entre los datos obtenidos y la dirección real a la que apunta el dispositivo.
  • timestamp: Tiempo en milisegundos en el que se tomó la muestra de datos.
No recomiento usar trueHeading y headindAccuracy por problemas documentados de compatibilidades.

El otro objeto del que podemos hacer uso es CompassError, que se pasa como parámetro a la función callback onError cuando llamamos a getCurrentHeading o watchHeading. Tiene la propiedad code, que puede valer, según el error:
  • CompassError.COMPASS_INTERNAL_ERR
  • CompassError.COMPASS_NOT_SUPPORTED
La explicación es bastante sencilla, o bien ha habido error al tomar los datos, o el dispositivo no dispone de brújula.


Ejemplo práctico

Vamos a implementar una brújula visual. ¿Cómo lo haremos? Mostraremos por pantalla una imagen cuya parte superior girará tantos grados como nos indique la propiedad magneticHeading del objeto CompassHeading, gracias a javascript y CSS. Una vez sabemos qué queremos y cómo implementarlo con las herramientas de las que disponemos, vamos a ello.

Partiremos del esqueleto que ya teníamos listo.
Por supuesto, podemos editar algunas opciones en el config.xml:

La imagen la guardaremos en www/img, yo he usado la siguiente imagen tomada de openclipart.org:
http://openclipart.org/image/300px/svg_to_png/188726/Compass_Rose_Arvin61r58.png




Una vez incluída en la carpeta de imágenes del proyecto, completamos el body del index.html tal que así:
<!DOCTYPE html>
<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/bootstrap.min.css">
        <link rel="stylesheet" type="text/css" href="css/index.css" />
        <title>Proyecto Phonegap</title>
    </head>
    <body>
        <div class="container">
            <h1 class="text-center">Brújula Phonegap</h1>
            <div class="row well text-center">
                <img img="brujula" src="img/brujula.png" alt="">
            </div>
        </div>
        
        <script type="text/javascript" charset="utf-8" src="cordova.js"></script>
        <script type="text/javascript" src="./js/jquery-1.11.0.min.js"></script>
        <script type="text/javascript" src="./js/bootstrap.min.js"></script>
        <script type="text/javascript" src="./js/fastclick.js"></script>
        <script type="text/javascript" src="./js/index.js"></script>
        <script type="text/javascript">
            window.addEventListener('load', function() {
                new FastClick(document.body);
            }, false);
            app.initialize();
        </script>
    </body>
</html>

Como vemos, sólo hemos añadido la etiqueta img cuya fuente es la imagen elegida, y mediante javascript le aplicaremos CSS con el giro que determine la brújula del dispositivo. Así queda el index.js:

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');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        //tomaremos el muestreo de datos cada segundo (1000 ms)
        watchID = navigator.compass.watchHeading(onSuccess, onError, {frequency: 1000});

        //función onSuccess que aplica CSS a la imagen de la brújula del html
        function onSuccess(heading){
            //guardo los datos obtenidos del objeto CompassHeading
            var orientacion = 360-heading.magneticHeading;
            //aplico con jquery una rotación que depende de la variable orientacion
            $("#brujula").css('-webkit-transform','rotate(' + orientacion + 'deg)');
        }
        //funcion onError que tiene el objeto CompassError pasado como parámetro
        function onError(compassError) {
            //me limito a informar del error
            alert('Error: ' + compassError.code);
        }
    }
};


Como en ejemplos anteriores, usamos cosas propias de Phonegap una vez que se ha capturado el evento deviceready. En este caso, como ya hemos explicado, la magia está en rotar la imagen de la brújula con la línea $("#brujula").css('-webkit-transform','rotate(' + orientacion + 'deg)');
El muestreo se actualiza cada 1000 milisegundos, como bien expresa la opción frequency, pero se puede cambiar al gusto.

Antes de compilar, importante, instalar el plugin en nuestro proyecto, que luego vienen los llantos.

Y finalmente compilamos en local o en la nube y testeamos el funcionamiento del programa. Yo he optado por compilar en local con la orden phonegap build android, y copiar el fichero HelloWorld-debug-unaligned.apk de la carpeta platforms/android/ant-build en el teléfono directamente.

Imagen de la aplicación en ejecución:

lunes, 2 de junio de 2014

Phonegap vs programación nativa, y breve reflexión

Programar haciendo uso de Phonegap tiene sus ventajas, pero también tiene sus desventajas, y según el caso, podría llegar a ser más conveniente realizar aplicaciones nativas para cada sistema.
En este artículo vamos a analizar los pros y contras que pueden ayudarnos a decantarnos por el uso de Phonegap o no.

Ventajas de Phonegap respecto a programación nativa para cada sistema:
  • Un código, varios ejecutables. La ventaja evidente de Phonegap es el ahorro de tiempo y esfuerzo ya que con un mismo código podemos obtener los  ejecutables para distintas plataformas. Además, supone un importante ahorro de dinero si estamos en un entorno empresarial.
  • Utilización de lenguajes de desarrollo web. Para obtener los ejecutables de nuestras aplicaciones, desarrollamos con javascript, html, y css. Si tenemos conocimiento de estas herramientas, prácticamente ya tenemos una base válida para empezar a programar en Phonegap. Quizá debamos añadir también un conocimiento muy básico de xml por aquello del fichero de configuración config.xml.
  • Aprovechamiento de frameworks, preprocesadores, etc muy potentes disponibles en desarrollo web. Como prácticamente estamos haciendo desarrollo web con algunos matices, podemos usar herramientas de ayuda como son preprocesadores css(Sass, Less, Stylus...), preprocesadores html(haml, jade, emmet...), preprocesadores javascript(coffeescript) o frameworks de desarrollo javascript(Angularjs, Emberjs, jquery...). Esto hace la programación web más amena y cómoda.
  • El aprendizaje de html, css y javascript que adquirimos al aprender a programar para Phonegap, en caso de no tenerlo, no sólo es bueno para escribir aplicaciones móviles, sino para desarrollo web en general. El saber tecnologías nuevas siempre es bueno pero en el caso de estas tres, hoy en día, es más importante aún.
  • Con Phonegap Build, tu código a la nube. Mediante utilidades como Adobe Phonegap Build podemos evitar tener kits de desarrollo(SDK) en nuestros ordenadores para compilar, y además tendremos más ordenadas las aplicaciones desarrolladas. En caso de trabajo en equipo, esta ventaja gana peso.
  • Desde la versión 3.0 de Phonegap, el rendimiento de las aplicaciones se ha optimizado. No van a igualar a las nativas, cierto, pero con las últimas actualizaciones, cada vez la diferencia de rendimiento entre las aplicaciones nativas y las realizadas con Phonegap/Cordova es menor. Sobre todo si las ejecutamos en terminales modernos.

También analizaremos los contras que existen, no todo iban a ser alegrías:

  • El que mucho abarca, poco aprieta(compatibilidades). PhoneGap nos hace de puente entre nuestro código web y el código nativo del dispositivo, por lo que nos da la habilidad de usar parte de su hardware. Pero no todas las funcionalidades del dispositivo están disponibles, dependiendo del sistema móvil en el que nos encontremos. Es importante conocer bien las compatibilidades de cada plugin y evento Phonegap antes de "implementarlas a lo loco", para no llevarnos sorpresas.
  • La depuración es mucho más tediosa que si programamos en nativo, y en parte gran culpa de esto lo tiene javascript.
  • Una aplicación nativa siempre será más rápida que una desarrollada con Phonegap/Cordova. La explicación a esto, es que Phonegap usa un webview en una app nativa, que levanta un "servidor local", y así permite que corra nuestra aplicación móvil.

Conclusión

Como en casi todo, la elección óptima siempre vendrá tras un estudio previo al desarrollo, teniendo en cuenta los requisitos y el comportamiento esperado de las aplicaciones.
Si lo que queremos desarrollar es una aplicación simple cuyos requerimientos tienden a ser muy bajos, y que no requiere usar características del sistema operativo de la plataforma(o un uso básico de las mismas), y que adicionalmente deseamos que funcione en diferentes tipos de dispositivos móviles, PhoneGap es una muy buena elección.
Por contra, si estamos hablando de aplicaciones que soportan funciones importantes de un sistema específico, y de alta complejidad, se debe considerar seriamente su implementación en plataforma nativa.

domingo, 1 de junio de 2014

Adobe Phonegap vs Apache Cordova

Pregunta importante, ¿Con qué framework desarrollo?

Como ya vimos en la introducción, Phonegap y Cordova son muy similares. De hecho, por ejemplo, la mayoría de órdenes de línea de comandos de ambos son iguales, pero unas empiezan por phonegap y otras por cordova. Lo que ocurre realmente es que el comando phonegap encapsula al comando cordova, añadiendo, en algunos casos, algún extra, pero en la mayoría de los mismos, estamos ante el mismo código fuente.

Hay que tener en cuenta que Cordova y Phonegap llevan apenas dos años existiendo por separado (este artículo se escribe en Junio 2014), por lo que es algo normal.
Entonces, si son tan parecidos, dará igual usar Cordova que Phonegap, ¿no? Todo depende.
Depende principalmente del uso que vayamos a hacer de los factores que diferencian a ambos frameworks. Este factor diferenciador es claramente Phonegap Build.
Si nuestra meta es aprender a usar alguno de estos frameworks, haciendo uso de un SDK, por ejemplo el de Android, y realizar alguna aplicación de prueba, no importa mucho si usamos Cordova o Phonegap. Con ambos vamos a compilar de forma local y a hacer pruebas sin notar diferencias entre un framework u otro más allá de que los comandos comienzan con palabras diferentes.
Si pretendemos desarrollar aplicaciones y compilarlas en varias plataformas, hacer uso de Phonegap Build puede ahorrarnos más de un dolor de cabeza. La ventaja principal es que no tenemos que disponer de ningún SDK para compilar en local nuestras aplicaciones. Nos ahorramos además, el no poder compilar para iOS desde un ordenador que no sea MAC(en local). Si estamos en MAC no tendríamos ese problema, de hecho, el SDK de Windows Phone está disponible para MacOS. Nuestro código fuente se envía a Phonegap Build, es compilado en la nube, y se nos da la opción de descargar los ejecutables para las distintas plataformas, todo muy simple. La desventaja de esto, es que el testeo de posibles errores es más lento, ya que la compilación e instalación de las aplicaciones pasa por más fases que si desde el SDK, en local, lanzamos un emulador para probarlas.

En mi opinión, la situación ideal es disponer en local de un SDK y testear la aplicación en un único sistema móvil(por ejemplo Android, como hemos hecho). Una vez que comprobemos que nuestra aplicación funciona correctamente, haciendo uso de Phonegap Build, obtenemos los ejecutables de nuestra aplicación en distintas plataformas. Esta situación ideal depende de los elementos que nos proporcionan Phonegap/Cordova, o mejor dicho, del conocimiento que tengamos de los mismos. Recordemos que algunos plugins, y ciertos eventos, no son compatibles con todas las plataformas móviles.
Esta situación es extensible al trabajo en equipos, en empresas por ejemplo. Dado que Phonegap Build nos permite pasarle el código de las aplicaciones por medio de GitHub, si usan Git como control de versiones todos los miembros de un equipo de desarrollo, el desarrollo en equipo es más ágil.
Por ejemplo, cada miembro testea los códigos de su rama(en otras palabras, la parte de la aplicación que le toca implementar) en local, hace los "merge" necesarios cuando crea oportuno a la rama master del proyecto, y cuando está lista una versión definitiva, teniendo ya creada una app en Phonegap Build enlazada al código GitHub del proyecto, basta con actualizar los ejecutables de cada sistema, y el sistema en la nube hace el trabajo por nosotros.

viernes, 30 de mayo de 2014

Plugin geolocalización

Este plugin de Cordova/Phonegap nos provee información útil sobre la localización física del dispositivo, por ejemplo, la latitud (1) o la longitud (2).
fuente: wikipedia.org

Este plugin funciona nutriéndose de información que le es suministrada tanto por el GPS del dispositivo, como por señales de red(Wifi, bluetooth, RFID, direcciónes IP y las IDs de GSM/CDMA.

Debemos saber que con el uso de este plugin podrían surgir problemas de privacidad, ya que el almacenamiento de esta información sensible de forma periódica podría revelar costumbres y hábitos de los usuarios. Por lo tanto, en la política de privacidad de nuestras aplicaciones, debemos dejar claro y explicar qué uso damos a los datos obtenidos por nuestras apps.

Soporte

Las plataformas que soportan este plugin son numerosas, y entre ellas encontramos las más importantes:

  • Amazon Fire OS
  • Android
  • BlackBerry 10
  • Firefox OS
  • iOS
  • Tizen
  • Windows Phone 7 and 8
  • Windows 8


Instalación

Para poder instalar en nuestro proyecto el plugin geolocation, debemos situarnos en la carpeta de la aplicación y teclear por consola:
phonegap plugin add org.apache.cordova.geolocation

Componentes

Al trabajar con este plugin, contamos con tres métodos para acceder a la información que nos interesa:

  • navigator.geolocation.getCurrentPosition
Uso: navigator.geolocation.getCurrentPosition(geolocationSuccess, [geolocationError], [geolocationOptions]);

Devuelve la posición actual a la función geolocationSuccess por un objeto Position pasado como parámetro. Opcionalmente podemos incluir dos parámetros más. Uno es la función callback que será llamada si sucede algún error(con un objeto PositionError como parámetro, en este caso), y un objeto que representa las opciones. Veremos qué opciones podemos pasarle más adelante.


  • navigator.geolocation.watchPosition
Uso: var watchId = navigator.geolocation.watchPosition(geolocationSuccess, [geolocationError], [geolocationOptions]);

Devuelve la posición del dispositivo cuando hay un cambio en ésta, como sucedía con el acelerómetro, usamos una variable para referenciar el intervalo. Puede incluirse una función callback de error por si algo no va bien, y el objeto con las opciones que hemos llamado geolocationOptions.


  • navigator.geolocation.clearWatch
Su uso es el siguiente: navigator.geolocation.clearWatch(watchID);
Detiene la espera de cambios en la posición del dispositivo, es decir, su refresco de datos que referencia el parámetro watchID, en este caso.


El objeto geolocationOptions

Podemos incluir este objeto en nuestras llamadas a los métodos del plugin, que contiene las opciones con las que se hará la llamada. Un ejemplo válido podría ser: { maximumAge: 3000, timeout: 5000, enableHighAccuracy: true };

¿Qué opciones podemos incluir?
  • enableHighAccuracy: valor booleano con el que establecemos una mayor precisión en la obtención de datos. En el ejemplo anterior estaríamos solicitando, además de las redes inalámbricas, el gps como fuente de datos.
  • maximumAge: Valor numérico que expresa el mayor tiempo en milisegundos que tendrá un valor cacheado, obtenido tiempo atrás, para que lo tenga en cuenta la aplicación. En el ejemplo anterior desechamos todo muestreo de datos con una antigüedad mayor a tres segundos.
  • timeout: Si en el tiempo especificado, expresado en milisegundos, no se ha llamado a la función geolocationSuccess de los métodos getCurrentPosition o watchPosition, pasará a llamarse a la función geolocationError(o como hubiésemos llamado a las funciones en nuestro programa).

El objeto Position

Es un objeto de sólo lectura que tiene como propiedades coords y timestamp. La propiedad coords devuelve a su vez un objeto, el objeto Coordinates(coordenadas). El objeto Coordinates tiene varias propiedades que son la información definitiva que normalmente vamos a solicitar con el uso de la geolocalización, tales como la latitud, o la altura.

Estas son las propiedades de Coordinates(todas son de tipo numérico):
  • latitude: Latitud en grados decimales.
  • longitude: Igual, pero con la longitud.
  • altitude: Expresa la altura.
  • accuracy: Nivel que expresa los metros de exactitud con los que se dan los valores de latitud y longitud.
  • altitudeAccuracy: Lo mismo pero con la altura.
  • heading: Dirección del viaje, expresado en grados en sentido de las agujas del reloj y tomando como 0 el norte real.
  • speed: Velocidad actual del dispositivo, expresada en metros por segundo.

El objeto PositionError

Disponemos del objeto PositionError para especificar las causas que provocaron un error al acceder a la geolocalización del terminal. cuenta con dos propiedades:
  • message: un mensaje en el que se describen los detalles del error encontrado.
  • code: puede ser uno de los códigos de error listados a continuación.
Constantes de PositionError
  • PositionError.PERMISSION_DENIED: es devuelto cuando el usuario no ha dado permiso para recolectar datos de posicionamiento.
  • PositionError.POSITION_UNAVAILABLE: es devuelto cuando el dispositivo no puede encontrar la posición. Suele ocurrir cuando no estamos conectados a una red, o no se encuentra el satélite.
  • PositionError.TIMEOUT: devuelto cuando el dispositivo no puede encontrar su posición en el tiempo que especificamos en las opciones de la llamada a los métodos anteriormente descritos que sirven para obtener la posición(getCurrentPosition o watchPosition).

Ejemplo práctico

Una vez vista la teoría, pasemos a ver cómo se usa este plugin a nivel práctico. Para ello, he preparado un ejemplo sencillo partiendo del esqueleto preparado en apartados anteriores.

Personalizamos un poco el config.xml...

Vamos a mostrar en pantalla de forma constante datos de posicionamiento, o el error que se produjera en caso de no poder acceder a éstos.

El html de la aplicación quedaría así(más bien la etiqueta body):
<body>
        <div class="container">
            <h1 class="text-center">Geolocalización</h1>
            <div id="longitud"></div>
            <div id="latitud"></div>
            <div id="mensaje"></div>
            <div id="error"></div>
        </div>
        
        <script type="text/javascript" charset="utf-8" src="cordova.js"></script>
        <script type="text/javascript" src="./js/jquery-1.11.0.min.js"></script>
        <script type="text/javascript" src="./js/bootstrap.min.js"></script>
        <script type="text/javascript" src="./js/fastclick.js"></script>
        <script type="text/javascript" src="./js/index.js"></script>
        <script type="text/javascript">
            window.addEventListener('load', function() {
                new FastClick(document.body);
            }, false);
            app.initialize();
        </script>
    </body>

No tiene mucho misterio, dejamos 4 divs listos para que se rellenen mediante javascript. El fichero index.js es el que se adjunta a continuación:
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');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        // función callback onSuccess
// este método recibe el objeto Position, que contiene los datos que nos interesan
function onSuccess(position) {
            longitud.html("Longitud: " + position.coords.longitude);
            latitud.html("Latitud: " + position.coords.latitude);
            divError.html("No hay errores");
            //damos información de latitud en palabras
            if(position.coords.latitude >= 0){
                mensaje.html("Latitud norte");
            }else{
                mensaje.html("Latitud sur");
            }
}

//La función callback onError recibe el objeto PositionError
function onError(error) {
            longitud.html("ERROR");
            latitud.html("ERROR");
            divError.html(error.code);
            mensaje.html("Mensaje: " + error.message);
}

        //guardamos los elementos DOM en variables
        var longitud = $("#longitud");
        var latitud = $("#latitud");
        var mensaje = $("#mensaje");
        var divError = $("#error");
        //referenciamos watchID para almacenar los datos cada 3 segundos
        //Si cada 3 segundos no recibimos una actualización de la posición, se lanzará un error
var watchID = navigator.geolocation.watchPosition(onSuccess, onError, { timeout: 3000, enableHighAccuracy: true });
    }
};

Despues de obtener el evento deviceready, procedemos con el funcionamiento de la aplicación. Rellenamos los div del html con los datos que nos proporciona el objeto Position, o PositionError, según se llame a onSuccess o a onError, en la penúltima línea del código. Podemos fijarnos que hemos incluído la opción timeout con valor de 3000. Si pasan tres segundos sin datos, por ejemplo por tener desactivado el gps y las redes, el error, que debe informarnos sobre la imposibilidad de acceder al sistema de posicionamiento, debe cambiar al error que lanza la opción timeout.

Todo este código está muy bonito, pero no serviría de nada si no instalamos el plugin antes de compilar.

Finalmente adjunto imágenes de la app ejecutándose en un terminal Android:

Sin acceso a ubicación:
 Error de código 2 (recordemos, POSITION_UNAVAILABLE):


Pasan 3 segundos sin datos, así que el error cambia a código 3 (TIMEOUT):


Activamos la localización:

Ahora todo va como esperábamos:

martes, 27 de mayo de 2014

Plugin para el acelerómetro

El acelerómetro es un elemento que detecta cambios en movimientos relativos en la orientación del dispositivo, en los ejes x, y, z. En otras palabras, mide la aceleración bajo el efecto de la gravedad. En este apartado vamos a conocer el plugin que nos permite acceder al acelerómetro del dispositivo.

fuente: http://w3c.github.io/deviceorientation/start.png


Este plugin se llama device-motion es compatible con las principales plataformas actuales, por lo que no habrá problemas de compatibilidad si lo implementamos en nuestras aplicaciones. Es soportado para:

  • Amazon Fire OS
  • Android
  • BlackBerry 10
  • Firefox OS
  • iOS
  • Tizen
  • Windows Phone 7 and 8
  • Windows 8
Métodos

Disponemos de tres métodos principales:

  • navigator.accelerometer.getCurrentAcceleration(accelerometerSuccess, accelerometerError);
Este método obtiene la aceleración en los ejes x y z, y los valores son enviados a la función callback accelerometerSuccess.
  • navigator.accelerometer.watchAcceleration
Con este método obtenemos la aceleración en un intervalo regular de tiempo. Veamos un ejemplo de uso:

var watchID = navigator.accelerometer.watchAcceleration(accelerometerSuccess, accelerometerError, [accelerometerOptions]);

En watchID guardamos una referencia al muestreo por intervalos de watchAccerelarion. Si todo va bien lanzamos la función accelerometerSuccess(se le pasa el objeto Acceleration como parámetro, explicado a continuación), en caso contrario, la función accelerometerError. AccelerometerOptions es un objeto en el que podremos incluir el siguiente parámetro, a modo de opción: frequency. Este valor se lo podemos pasar expresado en milisegundos(por defecto vale 10000) e identifica el intervalo de muestreo.

  • navigator.accelerometer.clearWatch
Detiene el muestreo de la aceleración que estaba referenciado por watchID. Su uso sería:
navigator.accelerometer.clearWatch(watchID);

Objetos

Tenemos el objeto Acceleration, que es pasado como parámetro en la función accelerometerSuccess, o onSuccess, o como queramos llamarla. Contiene los datos capturados por el acelerómetro en un momento determinado de tiempo. 
El objeto Acceleration tiene tres propiedades, x, y y z, que expresan los valores numéricos de la aceleración en cada eje (en m/s^2). Y también timestamp, que es el tiempo del instante de captura expresado en milisegundos.
Incluye el efecto de la gravedad(9.81 m/s^2), por lo que si el dispositivo yace boca arriba en una superficie horizontal los valores x,y,z serían 0,0, 9.81.

Ejemplo

Partimos del proyecto esqueleto que ya teníamos preparado en apartados anteriores. Lo copiamos y modificamos algunos valores en el fichero de configuración config.xml, básicamente para que no se vea "esqueleto" en el icono de la app en el móvil, cuando vayamos a ejecutarlo.


A continuación ejecutamos la orden que nos instala el plugin en nuestra carpeta de proyecto:
Phonegap plugin add org.apache.cordova.device-motion

Y ya tenemos el acelerómetro accesible desde nuestra aplicación. 

Vamos a implementar una aplicación simple que nos informa cada segundo (1000 milisegundos) de la aceleración en los tres ejes.

El contenido del body del html es el siguiente:
<div class="container">
            <h1 class="text-center">Acelerometro</h1>
            <div class="well">
                <div id="ejeX">
                    
                </div>
                <div id="ejeY">
                    
                </div>
                <div id="ejeZ">
                    
                </div>
            </div>
</div>
<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
        <script type="text/javascript" src="./js/jquery-1.11.0.min.js"></script>
        <script type="text/javascript" src="./js/bootstrap.min.js"></script>
        <script type="text/javascript" src="./js/fastclick.js"></script>
        <script type="text/javascript" src="./js/index.js"></script>
        <script type="text/javascript">
            window.addEventListener('load', function() {
                new FastClick(document.body);
            }, false);
            app.initialize();
</script>

He ignorado las líneas de los scripts. Como vemos se muestran 3 divs donde informaremos con javascript de las aceleraciones.

Este es el contenido del fichero javascript:
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');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        //iniciamos el muestreo
        //si hay exito, lanzamos la funcion mostrar
        //si hay error, lanzamos la funcion error
        //le pasamos el tiempo de muestreo en un objeto, como tercer parametro
        watchID = navigator.accelerometer.watchAcceleration(mostrar, error, {frequency: 1000});

        //función con la que muestro las aceleraciones
        //accedo al objeto Acceleration, concretamente a sus propiedades
        function mostrar(acceleration){
            $("#ejeX").html("Acceleration X: " + acceleration.x);
            $("#ejeY").html("Acceleration y: " + acceleration.y);
            $("#ejeZ").html("Acceleration z: " + acceleration.z);
        }

        //funcion error que dios quiera que no se ejecute
        function error() {
            alert('error!!');
        };
    }
};

Así luce la aplicación una vez compilada y ejecutada en nuestro terminal

Ejemplo 2

Lo correcto más bien sería decir "mejora del ejercicio anterior", ya que tomamos como base el ejemplo ya realizado y jugamos un poco con los datos que nos da el acelerómetro.
Vamos a implementar un objeto dentro de una caja que "se deje caer" según como tengamos orientado el teléfono(o tablet, o dispositivo compatible, en definitiva).
Añadimos los siguientes div debajo de los tres que ya teníamos:

<div id="caja">
                    <div id="punto">
                        
                    </div>
</div>

Con CSS vamos a hacer que se muestren como una caja y un punto(puntazo, para ser exactos).

#punto{
    width:50px;
    height:50px;
    background-color:black;
    position:absolute;
    top:0;
    left:0;
}

#caja{
    margin:10px auto;
    width:200px;
    height:200px;
    border:1px solid black;
    position:relative;

}



Ahora vamos a capturar esos div con javascript y a aplicar la función animate de jquery:

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');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        //iniciamos el muestreo
        //si hay exito, lanzamos la funcion mostrar
        //si hay error, lanzamos la funcion error
        //le pasamos el tiempo de muestreo en un objeto, como tercer parametro
        watchID = navigator.accelerometer.watchAcceleration(mostrar, error, {frequency: 200});

        //función con la que muestro las aceleraciones
        //accedo al objeto Acceleration, concretamente a sus propiedades
        function mostrar(acceleration){
            $("#ejeX").html("Acceleration X: " + acceleration.x);
            $("#ejeY").html("Acceleration y: " + acceleration.y);
            $("#ejeZ").html("Acceleration z: " + acceleration.z);

            //cargo en variables la caja, el punto móvil y los datos que me interesan
            var puntoMovil = $('#punto');
            var limite = $('#caja');
            var posicionPunto = puntoMovil.position();
            var bordeIzquierdo = 0;
            var bordeTop = 0;
            var bordeDerecho = limite.width() - puntoMovil.width() - 10; // 10 represents the 10px for the margin
            var bordeBottom = limite.height() - puntoMovil.height() - 10; // 10 represents the 10px for the margin
            
            //según los datos del acelerómetro muevo el punto a la derecha o izquierda, arriba o abajo
            //con CSS
            if( acceleration.x < -2 && posicionPunto.left <= bordeDerecho ) {
                puntoMovil.animate({
                    left:'+=10'
                },100);
            } else if( acceleration.x > 2 && posicionPunto.left > bordeIzquierdo ) {
                puntoMovil.animate({
                    left:'-=10'
                },100);
            }
            if( acceleration.y < -2 && posicionPunto.top > bordeTop ) {
                puntoMovil.animate({
                    top:'-=10'
                },100);
            } else if(acceleration.y > 2 && posicionPunto.top <= bordeBottom ) {
                puntoMovil.animate({
                    top:'+=10'
                },100);
            }
        }

        //funcion error que dios quiera que no se ejecute
        function error() {
            alert('error!!');
        };
    }

};

He cambiado la opción frequency a 200 milisegundos para que se vea más fluido el movimiento del cuadrado en la caja. También podemos observar que para que el cuadrado negro se mueva, los valores de los ejer x e y tienen que ser mayores que 2 o menores que -2, por lo que si dejamos el dispositivo "más o menos" plano, impediremos que se mueva.

Así se ve la aplicación después de compilar, ejecutar en un teléfono y tenerlo ligeramente inclinado hacia la derecha y hacia arriba:


Plugin para vibración (y ejemplo)

Vamos a enriquecer la aplicación de las vistas "principal" y "opciones" añadiendo la capacidad de vibrar el dispositivo en el que se ejecute. Vamos a hacer que el dispositivo vibre durante un periodo de tiempo determinado al pulsar los botones que cambian el color del texto en la pantalla de opciones.

En la página de GitHub podemos obtener información sobre este plugin, que nos proporciona la forma de hacer vibrar el dispositivo con Phonegap y Cordova. Antes de instalarlo, comprobamos que es compatible con las principales plataformas, por lo que es ventajoso su uso si pretendemos hacer la app compatible con varios sistemas móviles. Está soportado en:

  • Amazon Fire OS
  • Android
  • BlackBerry 10
  • Firefox OS
  • iOS
  • Windows Phone 7 and 8


Para instalarlo debemos ejecutar, en línea de comandos, y situados en la carpeta del proyecto, la siguiente orden:

phonegap plugin add org.apache.cordova.vibration



Con esto, nuestra aplicación ya es capaz de, con la programación adecuada, hacer vibrar nuestro dispositivo. Para hacerlo hay que ejecutar la siguiente línea de código, cambiando la variable t por un tiempo expresado en milisegundos:
navigator.notification.vibrate(t)

En iOS, este tiempo es ignorado y el dispositivo vibra durante un tiempo preestablecido.

Lo único que nos queda es llevar la orden de vibración al punto concreto de nuestra app en el que queremos que lo haga. Vamos al fichero index.js y añadimos lo siguiente a los eventos click de los botones "botonRojo" y "botonAzul":

...final del index.js...
    botonRojo.click(function(){
        $('p').addClass("fondoRojo");
        $('p').removeClass("fondoAzul");
        //el dispositivo vibrará durante medio segundo
        navigator.notification.vibrate(500);
    });
    botonAzul.click(function(){
        $('p').removeClass("fondoRojo");
        $('p').addClass("fondoAzul");
        navigator.notification.vibrate(200);
    });
}

He puesto tiempos de vibración diferentes para que se note la diferencia entre pulsar uno u otro botón.

Con esto ya sólo nos queda compilar, en local o en la nube, y probar la aplicación. 
Yo he optado por hacerlo en local desde consola y copiarme el .apk cuyo nombre termina en "unaligned" localizado en platforms\android\ant-build.



Plugins

Por defecto, al crear un proyecto Phonegap mediante línea de comandos en nuestro sistema, éste es muy básico. Se crea sólo la estructura de documentos, además de que usa la API justa y necesaria para funcionar. Para acceder a elementos hardware de los dispositivos móviles tales como cámara o vibración desde nuestra aplicación, necesitamos ampliar la API en nuestro proyecto. En Phonegap y Cordova, lo hacemos incluyendo plugins, que aumentan las capacidades de nuestra aplicación, pudiendo acceder a elementos a los que antes no se podía.

He aquí una lista con los plugins de Phonegap:

  • Battery Status: Sirve para monitorizar el estado de la batería.
  • Camera: Plugin que nos permite lanzar la cámara del dispositivo.
  • Contacts: Con este plugin podemos tener acceso a la agenda del teléfono.
  • Device: Obtenemos información básica del dispositivo.
  • Device Motion (Acelerómetro): Nos da acceso al acelerómetro del dispositivo.
  • Device Orientation (Compass): Podemos obtener la dirección a la que apunta el dispositivo.
  • Dialogs: Notificaciones.
  • FileSystem: Accedemos al sistema de ficheros mediante javascript.
  • File Transfer: También tendremos acceso al sistema de ficheros del dispositivo.
  • Geolocation: Hace que nuestras aplicaciones puedan localizar físicamente al usuario.
  • Globalization: Permite determinadas representaciones según las preferencias locales del usuario.
  • InAppBrowser: Lanza URLs a través de otra instancia de navegador en la app.
  • Media: Grabación y reproducción de audio.
  • Media Capture: Captura ficheros multimedia.
  • Network Information (Conexión): Nos da información sobre el estado de conexión del dispositivo.
  • Splashscreen:  Manipulamos las llamadas splash screen de las aplicaciones.
  • Vibration: Con esta API podemos hacer vibrar el dispositivo.


Si queremos añadir plugins para acceder a los elementos de nuestro dispositivo, podemos hacerlo mediante línea de comandos. Los comandos para añadir los plugins podemos consultarlos aquí. De este modo podemos añadir a nuestro código fuente las líneas necesarias para usar esos plugins sin temor de que haya fallos al compilar. Por ejemplo, si tecleamos el siguiente código:
phonegap local plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-battery-status.git
estaríamos añadiendo las utilidades necesarias para acceder a los datos de la batería del dispositivo tales como el nivel de carga.
Podemos saber qué plugins tenemos instalados en el proyecto haciendo uso del siguiente comando:
phonegap local plugin list