27 de febrer del 2018

Ionic: aplicació per saber l'hora de sortida/posta del sol (III)

Part III: Versió amb Geolocalització

Aquest article ens mostrarà com afegir geolocalització a l'aplicació per saber l'hora de sortida i posta de sol justament del lloc on ens trobem.

Fins ara hem vist:

Ara afegirem geolocalització per obtenir la posició actual del mòbil. La precisió variarà depenent de si està el GPS actiu, o si el posicionament és per les antenes de ràdio, wifi, etc... Però pel que necessitem nosaltres, serà més que suficient.

Afegim el mòdul de Geolocalització del Ionic al projecte:
$ ionic cordova plugin add cordova-plugin-geolocation \\
        --variable GEOLOCATION_USAGE_DESCRIPTION="Per posicionar-te"
$ npm install --save @ionic-native/geolocation
Ja veieu que es tracta d'un mòdul que requereix Cordova. Molts d'aquest mòduls només estan disponibles en un dispositiu físic (mòbil o tablet) i no en el navegador d'un PC. És important comprovar sempre la compatibilitat, per no tenir sorpreses.

En el cas que ens ocupa, Browser apareix llistat com a compatible, pel que sí podem utilitzar-lo en el navegador d'un PC (això sí, ens demanarà permís abans de permetre la localització).

Comprovem la documentació del propi mòdul per veure com utilitzar-lo:


Aquest codi l'afegirem a la pàgina home.ts. Però no ens hem d'oblidar d'afegir també el proveïdor Geolocation en el fitxer app.module.ts. Ens cas contrari, generaria un error de l'estil:


Ja que hi estem posats, organitzarem el codi separant la crida a l'API en una funció separada. Ens anirà bé, ja que la cridarem en un parell de situacions diferents.

També aprofitarem per generar un missatge indicant si la posició en la que es calcula és l'actual o la que hi ha per defecte (en cas de no poder usar la geolocalització). En aquest cas, donarem també un avís en la pantalla, en forma d'error o advertiment.

Bé, el codi quedaria com segueix:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';

import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Geolocation } from '@ionic-native/geolocation';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  API: string = "https://api.sunrise-sunset.org/json";
  
  // Estany de Sant Maurici
  lat: number = 42.582104;
  lon: number = 1.0008419;

  sunrise : string;
  sunset : string;

  posMsg : string;
  posError : boolean = false;

  constructor(  public navCtrl: NavController,
                public http: HttpClient,
                private geolocation: Geolocation
  )
  {  }

  ionViewDidLoad() {
    this.geolocation.getCurrentPosition().then((answer) => {
      //console.log(answer);
      this.lat = answer.coords.latitude;
      this.lon = answer.coords.longitude;
      this.posMsg = "Posició actual";
      this.getSunriseSunsetFromApi();

    }).catch((error) => {
       console.log('Error getting location', error);
       this.posMsg = "Per defecte";
       this.posError = true;
       this.getSunriseSunsetFromApi();
     });
  }

  getSunriseSunsetFromApi() {
    console.log("Calling API...");

    let data = {  lat: this.lat.toString(), 
                  lng: this.lon.toString(),
                  formatted: "0"
                };

    const params = new HttpParams({fromObject: data});
    const headers = new HttpHeaders().set("Accept", "application/json");
    let options = {headers: headers, params: params, withCredentials: false};

    this.http.get(this.API, options).subscribe(answer => {
      console.log(answer);

      if (answer['status']=="OK") {
        let date_options = {hour: "2-digit", minute: "2-digit"};

        this.sunrise = new Date(answer['results'].sunrise)
                            .toLocaleTimeString("ca-ES", date_options);
        this.sunset = new Date(answer['results'].sunset)
                            .toLocaleTimeString("ca-ES", date_options);

        console.log(this.sunrise);
        console.log(this.sunset);
      }
    },
    err => console.log(err)
    );    
  }
}

Observem els canvis:
  • [5] Importem el nou mòdul de geolocalització.
  • [21-22] Declarem les noves variables pel missatge del lloc i el possible error.
  • [26] Afegim el nou proveïdor de geolocalització.
  • [30-44] Ara, en carregar la pàgina provem d'obtenir la posició actual tal com en indica la documentació del mòdul. Si tot és correcte, obtenim la posició; en cas contrari, utilitzem la que tenim per defecte. En tot cas, actualitzem un missatge per l'usuari i una senyal d'error.
  • [46-75] La crida a l'API que calcula la sortida/posta de sol l'hem separat en una funció pròpia. A part d'això, no hi hem fet canvis.

També cal modificar el fitxer home.html per mostrar el nou missatge i el possible error:
 ...
<ion-content padding>
  <ion-list>
    <ion-item-divider color="light">Lloc: {{ posMsg }}</ion-item-divider>
    <ion-item color="light" *ngIf="posError">
      <h2>No s'ha pogut obtenir la posició actual</h2>
      <p>Verifica els permisos</p>
      <ion-icon name="warning" color="danger" item-left></ion-icon>
    </ion-item>
    ...

Només hi hem afegit un ion-item per mostrar un error en cas que no es pugui utilitzar la geolocalització. Fixem-nos en l'ús de l'operador *ngIf d'Angular. Amb ell, només es mostrarà l'element (i tot el seu contingut) si es compleix la condició indicada (en aquest cas, si la variable posError s'avalua com a certa [línia 41, quan salta un error en provar de geolocalitzar]).

El resultat, quan salta l'error, es pot veure a continuació:

Si mirem els missatges en la consola, podem veure que l'error que es produeix és un 403 (intent d'accedir a un recurs sense permisos adients).

En canvi, si li donem els permisos adequats, el resultat que obtenim és:

Noteu com el codi corresponent a l'error ja no es mostra ! En canvi, en la consola podem veure l'objecte que ens retorna el mòdul de geolocalització.

I d'aquesta manera tant senzilla hem aconseguit geolocalitzar l'aplicació. Com sempre, teniu tot el codi a la vostre disposició en el Github: v0.2 (amb geolocalització).

En la propera modificació, podríem afegir un calendari per escollir la data que volem, enlloc d'utilitzar l'actual.

Seguiu l'evolució de l'aplicació:

24 de febrer del 2018

Ionic: aplicació per saber l'hora de sortida/posta del sol (II)

Part II: Versió "clavada a ferro"

Aquest article ens mostrarà com fer una aplicació amb Ionic per saber l'hora de sortida i posta de sol en un lloc determinat.

En la primera part vàrem preparar el projecte inicial.

Ara farem una primera versió senzilla, on fixarem el lloc i el dia. Més endavant anirem modificant el codi per fer-lo més útil en general (i de passada anirem veien diferents tècniques).

En lloc de fer el càlcul de l'hora de sortida/posta de sol directament, l'aplicació es connectarà a internet per a obtenir-la. Amb una cerca ràpida a Google podem trobar una API que sembla feta a mida a https://sunrise-sunset.org/api.

En una primera versió, podem fer una petició simple amb:
 https://api.sunrise-sunset.org/json?
             lat=36.7201600&lng=-4.4203400&formatted=0

I obtindrem una resposta en JSON similar a:


Fixeu-vos que només necessitem la latitud i la longitud i fer una petició a l'API anterior per obtenir les dades que necessitem en la nostre aplicació.

En una aplicació Ionic, per poder fer peticions web, primer hem d'importar el mòdul HttpClientModule al fitxer app.module.ts. Quedaria com segueix:
 ...
 import { SplashScreen } from '@ionic-native/splash-screen';

 import { HttpClientModule } from '@angular/common/http';

 @NgModule({
  declarations: [
   ...
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  ...

Amb això ja podem implementar la funcionalitat desitjada en la pàgina home.ts:
 import { Component } from '@angular/core';
 import { NavController } from 'ionic-angular';

 import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';

 @Component({
  selector: 'page-home',
  templateUrl: 'home.html'
 })
 export class HomePage {
  API: string = "https://api.sunrise-sunset.org/json";
  
  // Estany de Sant Maurici
  lat: number = 42.582104;
  lon: number = 1.0008419;

  sunrise : string;
  sunset : string;

  constructor(  public navCtrl: NavController,
                public http: HttpClient )
  { }

  ionViewDidLoad() {
    console.log("Calling API...");

    let data = {  lat: this.lat.toString(), 
                  lng: this.lon.toString(),
                  formatted: "0"
                };

    const params = new HttpParams({fromObject: data});
    const headers = new HttpHeaders().set("Accept", "application/json");
    let options = { headers: headers, 
                    params: params, 
                    withCredentials: false
                  };

    this.http.get(this.API, options).subscribe(answer => {
      console.log(answer);

      if (answer['status']=="OK") {
        let date_options = {hour: "2-digit", minute: "2-digit"};

        this.sunrise = new Date(answer['results'].sunrise)
                               .toLocaleTimeString("ca-ES", date_options);
        this.sunset = new Date(answer['results'].sunset)
                               .toLocaleTimeString("ca-ES", date_options);

        console.log(this.sunrise);
        console.log(this.sunset);
      }
    },
    err => console.log(err)
    );
  }
 }

Observem l'estructura:
  • [4] Hem importat les classes que necessitarem.
  • [11-18] Declarem les variables internes a la classe, amb el seu tipus (gràcies, Typescript !) i opcionalment amb un valor per defecte. Aquí fixarem el valor de la latitud i la longitud del lloc que ens interessi a partir de les dades GPS (que podem obtenir a través de google maps, per exemple).
  • [21] Afegim el proveïdor per a les connexions a internet en el constructor.
  • [24] ionViewDidLoad és una funció que es crida una sola vegada, i és el lloc ideal per posar-hi codi d'inicialització. En aquest cas, com que les nostres dades no varien gaire sovint, les podem inicialitzar aquí. Val la pena consultar els cicles de vida d'una pàgina a Ionic a NavController.
  • [27-30] Preparem les dades en el format requerit per l'API. Necessitem convertir els números en strings.
  • [32-37] Generem els paràmetre i capçaleres de la nostre petició a l'API, i finalment ho posem tot plegat en un objecte options.
  • [39-55] Fem la petició i quan obtenim la resposta, extraiem les dades que volem mostrar. En cas d'error, traiem un missatge per la consola.
Val la pena comentar que la petició web retornarà les dades al cap d'un temps indeterminat. Per això, el valor es retorna a través d'un Observable, al qual ens subscrivim per rebre el seu valor quan estigui disponible.

Fixem-nos com el fitxer home.ts actua com a controlador de les dades de la classe. Per la seva banda, la visualització d'aquestes es fa en el fitxer home.html:
<ion-header>
  <ion-navbar>
    <ion-title>Sortida/posta de Sol</ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-list>
    <ion-item-divider color="light">Lloc</ion-item-divider>
    <ion-item>
      <ion-label>Latitud</ion-label>
      <ion-note item-right>{{ lat }}</ion-note>
    </ion-item>
    <ion-item>
        <ion-label>Longitud</ion-label>
        <ion-note item-right>{{ lon }}</ion-note>
      </ion-item>
  
    <ion-item-divider color="light">Data</ion-item-divider>
    <ion-item>
      <ion-note item-right>Avui</ion-note>
    </ion-item>

    <ion-item-divider color="light">Horari</ion-item-divider>
    <ion-item>
      <ion-icon name="sunny" color="orange" item-left></ion-icon>
      <ion-note item-right>{{ sunrise }}</ion-note>
    </ion-item>
    <ion-item>
      <ion-icon name="moon" color="blue" item-left></ion-icon>
      <ion-note item-right>{{ sunset }}</ion-note>
    </ion-item>

  </ion-list>
</ion-content>

L'estructura utilitza tags tipus html que, de fet, són components pre-definits de Ionic. Amb això podem construir una interfície d'usuari agradable sense molt d'esforç: amb llistes, icones, etiquetes, etc...

El més destacable és com podem utilitzar en el fitxer html les variables que tenim definides en el nostre controlador, gràcies als operadors d'enllaç de dades d'Angular. En la nostre aplicació només utilitzem l'operador d'interpolació, {{ var }}, que serà substituït en la pàgina de sortida pel valor que tingui la variable en la classe en cada moment.

Bé, ha arribat el moment de veure el resultat:
 $ ionic serve -c -s

El resultat ens apareixerà en una finestra nova al navegador:


Observem com gràcies a les "Eines de desenvolupador" podem veure els missatges que surten per la consola. En el cas d'un objecte com el que obtenim de la petició a l'API [línia 40], el podem expandir i veure'n tots els detalls.

Però anem a veure com es genera l'aplicació mòbil. De fet, si tenim els SDK d'Android i el XCode ja instal·lats i configurats, és tan senzill com:
 $ ionic cordova build android
 $ ionic cordova build ios

El resultat es pot veure a continuació:


A l'esquerra hi ha una captura de pantalla directe des d'un Samsung S6, i a la dreta el resultat en l'emulador d'un iPhone 7.

Tal com es pot observar, Ionic ens permet obtenir una aplicació que corre en els dos principals sistemes operatius mòbils (i en la web, tot sigui dit de passada) a partir d'un mateix codi.

I parlant de codi, si us animeu a provar l'aplicació, la teniu disponible al GitHub: v0.1 (clavada a ferro). Proveu-la, i ja em comentareu.

Més endavant anirem modificant l'aplicació per permetre variar el lloc i la data, la qual cosa ens permetrà veure noves tècniques de programació, ús del GPS en els dispositius mòbils, etc...
Seguiu l'evolució de l'aplicació:

22 de febrer del 2018

Ionic: aplicació per saber l'hora de sortida/posta del sol (I)

Part I: Preparació

Per mostrar les excel·lències de Ionic, anem a fer una aplicació senzilla. En aquest cas, una que ens indiqui a quina hora surt i s'amaga el sol en un lloc determinat, en un dia determinat. Pot ser interessant per una sortida romàntica o en el seu defecte, per fotògrafs solitaris ;-)

Si no coneixeu Ionic o encara no l'heu instal·lat, podeu llegir el meu article al respecte.

En primer lloc, crearem l'aplicació partint d'una base que ja ens dona el propi Ionic. Per això fem:
$ ionic start SunriseSunset

Escollim la base 'tabs', indiquem que volem generar codi iOs/Android i comença la instal·lació. Quan ens pregunti si volem integrar l'aplicació amb Ionic Pro, li podem dir que no, de moment.

El resultat és un directori SunriseSunset ja preparat. Podem comprovar que tot ha anat bé amb:
$ cd SunriseSunset
$ ionic serve -c -s

Això compilarà l'aplicació i ens obrirà una finestra en el navegador per defecte. En el Chromium, obrim les "Eines per a desenvolupadors" i la barra de selecció de dispositius i se'ns mostrarà alguna cosa similar a:


Podem interactuar amb les icones tal com ho faríem en un dispositiu mòbil. També veurem els missatges que vagin apareixent en la consola, la qual cosa ens serà molt útil per depurar el codi, més endavant.

Si comprovem els missatges que ens ha donat durant la creació del directori font, podrem veure com ja està tot preparat per treballar amb el git (que és sistema de control de versions més utilitzat en l'actualitat). Per defecte està pensat per treballar amb Ionic Pro, però res no ens impedeix de fer-ho en el nostre compte personal a Github.

Primer hem de crear un repositori remot a Github i després associar-hi el nostre directori de treball. Ho podem fer seguint les indicacions de https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ amb:
$ git config --global user.email "el_teu_correu@email.com"
$ git config --global user.name "El Teu Nom"
$ git config --global push.default simple
$ git commit -m "Ionic. Template tabs."
$ git remote add origin https://github.com/_usuari_/SunriseSunset.git
$ git push -u origin master

Tot seguit comprovem com el nostre repositori a Github ja conté el codi:

Per editar el codi, recomano el VisualStudioCode. És un editor lleuger que ens permetrà treballar sense distraccions i de forma eficient, amb compleció de codi, ajuda en línia i una multitud d'altres funcionalitats que anireu descobrint sobre la marxa.


En la imatge es pot veure l'estructura de fitxers del projecte (a l'esquerra) i el codi HTML modificat amb tags propis del Ionic.

Amb això acabem les tasques de preparació i ja ho tenim tot a punt per a implementar el que vulguem.

Ben aviat comprovarem el senzill que resulta fer l'aplicació que ens hem proposat. Serà en la segona part :-)

Seguiu l'evolució de l'aplicació:

18 de febrer del 2018

Ionic: aplicacions multi-plataforma amb el mateix codi

Com qui no vol la cosa, porto treballant amb Ionic des de fa gairebé un any i mig. Vaig arribar a Ionic v1 buscant una plataforma que permetés desenvolupar aplicacions multi-plataforma (Android, iOS i web) amb els mínims canvis de codi. Justament això era el que oferia Ionic v1.

Però no tot era perfecte. Al cap d'un mes, vaig voler provar una de les betes de Ionic 2. I ja no hi va haver tornada enrere. La nova versió de Ionic afegia el que li mancava a Ionic v1: una millor estructuració del codi, orientació a objectes i SOBRETOT verificació de tipus gràcies a la substitució del javascript pel Typescript com a llenguatge base de programació.


La ràpida evolució d'Ionic m'ha provocat alguns inconvenients quan s'han introduït canvis que trencaven la compatibilitat amb el codi anterior, però en general ha evolucionat en la bona direcció.

És interessant destacar que la base del Ionic és Angular i les tecnologies web: javascript, HTML5 i CSS. Això vol dir que es beneficia directament de l'evolució dels navegadors i de tot el suport a internet en general. I la propera versió, Ionic 4, generarà directament components web, pel que es podrà utilitzar amb qualsevol framework enlloc d'Angular.

Bé, crec que ja he fet prou venda del producte.

Properament veurem com de fàcil és fer una aplicació mòbil amb Ionic. Però mentrestant, si voleu provar Ionic, us aconsello que seguiu les instruccions d'instal·lació per a la vostra plataforma a https://ionicframework.com/getting-started.

Com a consell addicional, instal·leu primer el NVM seguint les instruccions de https://ionicframework.com/docs/developer-resources/using-nvm/. Això us permetrà canviar fàcilment les versions del Node, necessari per executar Ionic, i evitarà problemes de permisos (o haver d'utilitzar el sudo per instal·lar paquets).

Si teniu dubtes, podeu utilitzar els comentaris (o anar directament al fòrum de Ionic). Ànims !