Archivos de la categoría iOS

Ionic 2 y Angular 2 – Tercera parte

Después de la primera y la segunda parte, ahora nos toca esta tercera en la que crearemos un servicio para que nuestra app puede obtener datos de manera sencilla.

Lo primero es declarar el servicio y nuestra clase Query del capítulo anterior en el fichero app/app.module.ts.

Primero importamos los módulos:


import { ApiService } from '../services/api.service';
import { Query } from '../services/query';

Y después los añadimos dentro del array de providers del @NgModule:

 providers: [
    StatusBar,
    SplashScreen,
    Query, // <--
    ApiService, // <--
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]

Una vez hecho esto vamos a crear el archivo services/api.service.ts donde lo primero que haremos será importar nuestra clase Query y por otro lado importar la clase Injectable para que podamos usar este servicio en otras partes de la app.

import { Injectable } from '@angular/core';
import { Query } from './query';

A continuación defino la función que hará uso de la clase Query y un mapper para mis datos:

const doQuery = (query, params, mapper) => {
  return query.post('https://miapi.com/v1/', params, mapper);
};

const mapper = data => {
  data.var3 = data.var1 + data.var2;
};

Si no queremos procesar los datos que recibimos y nos sirven tan cual llegan la función de mapper quedaría de la siguiente forma:

  const mapper = data => data;

Y por último defino la clase del servicio en sí.

@Injectable()
export class ApiService {

  constructor(private query: Query) {
    this.query = query;
  }

  metodo1() {
    return doQuery(this.query, {}, mapper);
  }

  metodo2({param1, param2}) {
    return doQuery(this.query, {param1, param2}, mapper);
  }
}

En lo que hay que fijarse en esta clase es la llamada al constructor, la cual recibe como parámetro query que es de tipo Query y que ésta se asigna al objeto this, que es la clase en sí para poder usarla luego dentro de los distintos métodos.

Aquí tenéis el código completo del servicio:

import { Injectable } from '@angular/core';
import { Query } from './query';

const doQuery = (query, params, mapper) => {
  return query.post('https://miapi.com/v1/', params, mapper);
};

const mapper = data => {
  data.var3 = data.var1 + data.var2;
};

@Injectable()
export class ApiService {

  constructor(private query: Query) {
    this.query = query;
  }

  metodo1() {
    return doQuery(this.query, {}, mapper);
  }

  metodo2({param1, param2}) {
    return doQuery(this.query, {param1, param2}, mapper);
  }
}

En la próxima parte veremos cómo crear una página, utilizar el servicio y mostrar los datos recibidos.

Ionic 2 y Angular 2 – Primera parte

Después de un tiempo resistiendome y engañandome a mi mismo de que Ionic con Angular 2 no era lo que yo necesitaba para crear nuevos proyectos. Después de dar mis primeros pasos con React y sobre todo después de asistir a un curso, muy bueno por cierto, de Angular 1.5.X orientado a componentes. Por fin, hace un par de semanas, le di una oportunidad a la nueva versión, reescrita desde cero y con Typescript, de Ionic.

Para probar cómo funcionaba y qué concimientos iba a necesitar para en un futuro no muy lejano implementar una app para algún cliente, vi que necesitaba adquirir unos conceptos básicos que me permitieran desarrollar desde cero una app sencilla con unas pocas pantallas, un par de listas, almacenamiento local y llamadas a una API externa para obtener contenido.

Por todo ello, voy a escribir una serie de artículos en los cuales voy a ir contando qué componentes básicos he usado, cómo he hecho la app y qué pasos he seguido para adquirir todos los conocimientos necesarios para crear una app con Ionic.

En esta primera parte voy a hablar de la estructura de la app, es decir de cómo organizar el proyecto para que luego cuando haya que ir añadiendo funcionalidad no tengas todo desperdigado y tengas que repetir código una y otra vez.

Lo primero que hice, aunque parezca muy básico, fue seguir la página de Get started,  con lo que instale ionic y cordova, y escogí la plantilla de tabs para crear mi app:

npm install -g cordova ionic

ionic start myApp tabs

Una vez hecho esto observé que la estructura directorios había cambiado un poco, para empezar ya no metes el código en www, sino que existe un directorio src en el cual metes todo el código de la app, antes tenías por un lado el los js en www/js y las plantillas en www/templates, ahora como estás orientando tu programación a componentes tienes en src un directorio pages que a su vez contiene un directorio por cada componente y dentro de estos últimos, tres archivos: plantilla, estilos y javascript(typescript):

home.html home.scss home.ts

Seguimos analizando y vemos una carpeta app en la que tenemos los mismo elementos que en una página pero la parte de funcionalidad dividida en componente, modulo y principal:

app.component.ts	app.module.ts		main.ts
app.html		app.scss

Esto se correspondería con el archivo app.js que teníamos antes y donde inyectábamos todos los módulos necesarios para la app, así como la configuración de la app y la definición de las rutas, y alguna que otra directiva.

El archivo app.component.ts, define el componente principal sobre el cual se va construir la app, yo lo he dejado tal y como estaba y no me ha hecho falta modificarlo.

En cambio el archivo app.module.ts, lo he tenido que tocar y mucho ahí es donde tienes que importar todos los componentes que vayas a usar en la app, tanto páginas completas, como partes de estas, así como servicios.

Dentro del NgModule hay 5 apartados:

  • declarations,
  • imports
  • bootstrap
  • entryComponents
  • providers

En declarations van el componente de la app, así como todos los componentes de páginas y tabs, imports y bootstrap no los he tocado. en entryComponents vuelve a estar la páginas y los tabs. Y en providers he añadido los servicios que he ido creando.

main.ts, sinceramente ni había mirado lo que tenía dentro, así que se queda sin modificar.

Con todo esto me di cuenta que la estructura que crea Ionic por defecto no está mal y se puede usar para desarrollar una app medianamente compleja, el único añadido que he hecho ha sido crear a la altura de pages un directorio services que es dónde van a ir todos los servicios de la app.

En la siguiente parte hablaré de cómo hacer llamadas a una API externa para obtener los datos que queremos visualizar en la app.

 

SuperSefile

En el último proyecto que he realizado por encargo, he tenido que utilizar una serie de herramientas que me han parecido interesantes y que comparto aquí por si alguien las necesita para otros proyectos.

Es una app de fotografía, en la cual sacas una foto, la puedes firmar, y después puedes componer la firma con la foto e incluso añadir algún que otro texto incluyendo emojis.

Para empezar decidí no usar ningún framework de javascript, ya que quería que la app fuera lo más ligera posible, si que es cierto que he tenido que sacrificar un poco la legibilidad del código de la misma, pero siendo estricto en la nomenclatura de las funciones he conseguido estructurar bien el código y que no fuera muy difícil su depuración.

El único sacrificio que he tenido que hacer ha sido meter jQuery, ya que la librería que he usado para la firma era un plugin de jQuery, si bien es cierto que lo usa para un par de accesos y eventos, desvilcularla de jQuery o usar una librería más liviana, tipo Zepto.js, no era mi prioridad y podría meterme en jardines que realmente no quería pisar.

Para la firma entonces usé el plugin de jQuery, jSignature. Básicamente lo que hace es crear un canvas en el cual se puede realizar la firma, el algoritmo que usa es bastante bueno y tienen un rendimiento aceptable incluso en Android 4.0.4.

Para la composición de la firma y la foto use una librería con muchas más funcionalidades de las que necesitaba pero con vistas a una posible evolución de la app. La librería es Fabric.js. Como pone en su web es un modelo de datos intercativo que proporciona una API para realizar operaciones sobre un objeto de tipo canvas. La verdad es que es muy completa y tiene un montón de ejemplos a parir de los que empezar a trabajar.

Para finalizar me hacía falta un colorPicker y ya que tenía que usar jQuery busqué uno que se amoldara a mis necesidades y que fuera un plugin de jQuery: Spectrum. Al igual que Fabric.js, este plugin tiene más funcionalidades de las que necesito, pero lo elegí por poder implementar las mismas en una futura evolución de la app.

Por lo demás el resultado lo podéis ver aquí:

https://play.google.com/store/apps/details?id=com.javray.SuperSelfie
https://itunes.apple.com/es/app/superselfie/id957281514

Compartir con Whatsapp en Phonegap (iOS y Android)

Una de las funcionalidades que últimamente me están demandado para las aplicaciones es la de poder compartir un texto o un foto por Whatsapp.

Al ser un producto de terceros y no tener una API pública, mi respuesta siempre era la de que no era posible. Hasta ahora, o hasta hace relativamente poco tiempo.

Buscando si era posible, me encontré con el siguiente FAQ la propia aplicación:

http://www.whatsapp.com/faq/en/iphone/23559013

Esto ya me dio una pista de hacia dónde orientar mis pruebas de cómo implementarlo.

La parte fácil es la de compartir texto, ya que usando el esquema de URL whatsapp:// se puede usar en ambas plataformas y es tan sencillo como poner un enlace o abrir una página:

<a href="whatsapp://send?text=Hello%2C%20World!">Whatsapp></a>
window.open('whatsapp://send?text=Hello%2C%20World!');

Para las imágenes las cosas se complica y tendremos que usar métodos diferentes según la plataforma.

iOS

Aquí vamos a usar la API de Document Iteration, básicamente es una API que nos permite sacar las aplicaciones que pueden abrir un tipo de documento, como sucede con Dropbox cuando te bajas un archivo y te da la opción de editarlo con aplicaciones externas que tengas instaladas.

Para usar esta API nos instalaremos el siguiente plugin de Phonegap:

https://github.com/phonegap/phonegap-plugins/tree/master/iOS/ExternalFileUtil 

Este plugin lo único que hace es abrir un cuadro con las aplicaciones compatibles con el tipo de documento que queremos compartir. Yo he modificado un par de cosas ya que el plugin devolvía siempre una cadena vacía abriera o no el cuadro, lo cual no es muy útil ya que no permite controlar si está o no instalado el Whatsapp en el dispositivo.

La modificación es en el fichero CDVExternalFileUtil.m y hay que cambiar la línea 64, de esto:

[controller presentOpenInMenuFromRect:rect inView:cont.view animated:YES];

a esto:

BOOL result = [controller presentOpenInMenuFromRect:rect inView:cont.view animated:YES];

Y la línea 66, de esto:

pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: @""];

a esto:

pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString: (result ? @"YES" : @"NO")];

Una vez hechos esos cambios la manera de usar el plugin es la siguiente:

ExternalFileUtil.openWith('http://example.com/imagen.wai', "net.whatsapp.image", 
                function(result) { 
                    if (result == 'NO') {
                        // No está instalado el Whatsapp
                    }
                }
            );

El fichero imagen.wai es un JPG pero renombrado a .wai que es la extensión que usa el Whatsapp para saber que es una imagen. Para vídeos es .wam y para sonidos .waa.

Android

Aquí lo tenemos algo más fácil ya que mediante Intents podemos compartir una imagen.

Usaremos el siguiente plugin para poder lanzar Intents:

https://github.com/phonegap/phonegap-plugins/tree/master/Android/WebIntent

También es necesario modificarlo un poco ya que no tiene implementada la opción de lanzar directamente un Intent asociado a un paquete.

Por tanto los cambios a realizar son en el fichero  WebIntent.java, en la línea 54, añadir debajo la siguiente línea:

String pack = obj.has("package") ? obj.getString("package") : null;

En la línea 70, cambiar esto:

startActivity(obj.getString("action"), uri, type, extrasMap);

por esto:

startActivity(obj.getString("action"), uri, type, pack, extrasMap);

En la línea 158, añadir debajo lo siguiente:

if (pack != null) {
  i.setPackage(pack);
}

El uso sería el siguiente:

var extras = {};
                            extras[window.plugins.webintent.EXTRA_STREAM] = imagen_b64;

                            window.plugins.webintent.startActivity({ 
                                action: window.plugins.webintent.ACTION_SEND,
                                type: 'image/*', 
                                package: 'com.whatsapp',
                                extras: extras 
                                }, 
                                function() {
                                    // OK
                                }, 
                                function() {
                                    // El Whatsapp no está instalado.
                                }
                            );

La variable imagen_b64 será una imagen codificada en base64, podremos usar un fileReader si la imagen está en el dispositivo o un fileTransfer si está en un servidor.

[Editado]

Parce que lo del esquema de URL solo lo han implementado para iOS, para Android habrá que usar también el WebIntent:

var extras = {};
                            extras[window.plugins.webintent.EXTRA_TEXT] = 'Hola mundo';

                            window.plugins.webintent.startActivity({ 
                                action: window.plugins.webintent.ACTION_SEND,
                                type: 'text/plain', 
                                package: 'com.whatsapp',
                                extras: extras 
                                }, 
                                function() {
                                    // OK
                                }, 
                                function() {
                                    // El Whatsapp no está instalado.
                                }
                            );

[/Editado]