Archivo de la etiqueta: geolocalización

Como mejorar el rendimiento de ionic en iOS y solución al problema de las peticiones http CORS

En el siguiente enlace tienes el índice para acceder al resto de entradas de este tutorial:

 

¡¡Atención!! este tutorial se basa en ionic 3 y está desactualizado por lo que es posible que los ejemplos no funcionen en la última versión de ionic, haz click aquí para acceder a un tutorial mas actual de Ionic.

Hola a todos:

Esta semana he estado trabajando en una aplicación con ionic para un cliente.

La aplicación funcionaba estupendamente en todos los dispositivos Android donde la he probado, evidentemente en dispositivos más antiguos el funcionamiento era un poco menos fluido pero aceptable en cualquier caso, sin embargo a la hora de probar la app en un dispositivo iOS, concretamente en un iPhone 6 que tengo para probar las apps en un dispositivo real me he llevado una decepción.

La aplicación tiene que mostrar una lista con imágenes e información bastante grande, además tiene un buscador para filtrar el listado por varios campos.

El scroll no iba muy fino a pesar de utilizar [virtualScroll] para el listado, cuando filtraba el listado por algunos campos a la hora de refrescar el contenido del listado iba a trompicones y tardaba en responder… en fin, la experiencia de usuario dejaba bastante que desear.

Lo primero que he hecho es pulir el código todo lo que he podido intentando que sea lo más eficiente posible, pero no ha sido suficiente.

Investigando un poco en el blog oficial de ionic me he encontrado con WKWebView.

Podéis leer la entrada del blog de ionic en este enlace:

http://blog.ionic.io/cordova-ios-performance-improvements-drop-in-speed-with-wkwebview/

Como sabemos ionic utiliza apache cordova que a su vez utiliza la webview del sistema para mostrar el contenido de nuestra aplicación.

Actualmente, la plataforma iOS proporciona dos webviews diferentes.

Está la webview más antigua (y más lenta) llamada “UIWebView” y otra mas nueva llamada “WKWebView“.

El navegador web predeterminado de iOS es Safari, internamente Safari utiliza WKWebView, sin embargo ionic debido a diversas incompatibilidades y problemas  técnicos utiliza UIWebView.

WKWebView es más rápida que UIWebView, además Apple proporciona actualizaciones en cada versión de iOS.

Para utilizar WKWebView solo tenemos que instalar en nuestra aplicación un plugin que proporciona ionic-team.

Para instalar el plugin desde consola ejecutamos el siguiente comando:

ionic cordova plugin add https://github.com/ionic-team/cordova-plugin-wkwebview-engine.git --save

Solo con instalar este plugin mi aplicación iba muchísimo mas fluida en mi iPhone.

Probad vuestra aplicación con este plugin y si todo va bien perfecto, sin embargo yo tuve problemas con CORS  (Cross Origin Resource Sharing) al hacer peticiones al servidor ya que yo no tenia acceso para cambiar nada en el servidor y por lo tanto no podía cambiar las cabeceras de respuesta del servidor para aceptar peticiones cross origin.

Esto lo podemos resolver de dos maneras, utilizando en plugin de ionic native HTTP: https://ionicframework.com/docs/native/http/

El único problema es que sólo funciona en el dispositivo y no proporciona toda la potencia del servicio Http de Angular.

Para solucionar esto podemos utilizar ionic-native-http-connection-backend que podéis encontrar en github: https://github.com/sneas/ionic-native-http-connection-backend

La forma de instalarlo sería:

npm install ionic-native-http-connection-backend --save
ionic cordova plugin add cordova-plugin-http2

Después en app.module.ts tendríamos que añadir lo siguiente:

import { NgModule } from '@angular/core';
import { NativeHttpFallback, NativeHttpModule } from 'ionic-native-http-connection-backend';
import { RequestOptions, Http } from '@angular/http';

@NgModule({
    declarations: [],
    imports: [
        NativeHttpModule
    ],
    bootstrap: [],
    entryComponents: [],
    providers: [
        {provide: Http, useClass: Http, deps: [NativeHttpFallback, RequestOptions]}
    ],
})
export class AppModule {
}

Una vez hecho esto ya podemos realizar peticiones http sin problemas, si quieres saber como relizar peticiones http con ionic puedes consultar el siguiente post: Tutorial de Ionic – Peticiones http – API REST

Con estas dos cosas he conseguido que la aplicación funcione correctamente y con una fluidez mas que aceptable en iOS.

Bueno, por hoy aquí lo dejo con estos pequeños consejos, espero que os sea útil.

Si necesitas desarrollar una aplicación móvil no dudes en solicitarme un presupuesto sin compromiso:

Tutorial de Ionic – Firebase – parte 2: Database – Guardar nuestros sitios en la nube.

En el siguiente enlace tienes el índice para acceder al resto de entradas de este tutorial:

 

¡¡Atención!! este tutorial se basa en ionic 3 y está desactualizado por lo que es posible que los ejemplos no funcionen en la última versión de ionic, haz click aquí para acceder a un tutorial mas actual de Ionic.

Hola a todos:

El el post anterior vimos como autenticar un usuario en firebase con email y contraseña, hoy vamos ha ver como utilizar Firebase Database para guardar nuestros sitios en la nube.

Para ello vamos a crear un provider para gestionar nuestros sitios en firebase Database, por lo tanto desde consola nos situamos dentro de la carpeta de nuestro proyecto y creamos en nuevo provider:

ionic g provider firebaseDb

Ahora editamos el archivo firebase-db.ts que se acaba de generar dentro de la carpeta providers/firebase-db, eliminamos el import Http y rxjs/add/operator/map e importamos AngularFireDatabase, FirebaseListObservable  y nuestro provider AuthProvider quedando de la siguiente manera:

import { Injectable } from '@angular/core';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { AuthProvider } from '..auth/auth';
/*
  Generated class for the FirebaseDbProvider provider.

  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular DI.
*/
@Injectable()
export class FirebaseDbProvider {

  constructor(public afDB: AngularFireDatabase, public auth: AuthProvider) {
    console.log('Hello FirebaseDbProvider Provider');
  }
}

Como necesitamos el id del usuario lo primero que vamos ha hacer es añadir la función getUser al provider auth que creamos en el post anterior, por lo tanto editamos auth.ts y añadimos esta función:

// Obtenemos el id de usuario.
 getUser(){
    return this.afAuth.auth.currentUser.uid;
 }

Bien, ahora que ya podemos obtener el id de usuario vamos a añadir en el archivo firebase-db.ts un método para guardar nuestros sitios en Firebase database:

guardaSitio(sitio){
     sitio.id  = Date.now();
     return this.afDB.database.ref('sitios/'+this.auth.getUser()+'/'+sitio.id).set(sitio)
  }

Como vemos la función recibe como parámetros  sitio que será un objeto con los datos de nuestro sitio.

Al objeto sitio le añadimos un campo id para identificarlo y así poder luego modificarlo. Como necesitamos que el id sea diferente cada vez vamos a utilizar Date.now() que nos devuelve los milisegundos transcurridos desde el 1 de enero de 1970, con esto  nos aseguramos que no se repita el id, a no ser que seas capaz de guardar dos sitios en menos de un milisegundo ;-P.

En firebase se guarda la información con estructura de árbol en formato JSON. Para acceder a ella tenemos que hacer referencia a la “ruta” a la que queremos acceder.

En este caso le estamos diciendo que guarde nuestro sitio con esta estructura ‘sitio/_id_usuari_/_id_sitio_/_sitio_’.

El id de usuario lo obtenemos con la función que acabamos de definir en  AuthProvide  this.auth.getUser().

Dentro de sitio colgarán los diferentes id de usuarios de los cuales a su vez colgarán los diferentes sitios de cada usuarios.

Para verlo mas claro en el modal nuevo-sitio vamos a modificar el método guardarSitio para que en lugar de guardar el sitio en la base de datos local SQlite lo guarde en firebase y podamos ver la estructura de como se guarda la información en firebase.

Editamos modal-nuevo-sitio.ts y hacemos los siguientes cambios:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController  } from 'ionic-angular';
import { Camera, CameraOptions } from '@ionic-native/camera';
// import { DbProvider } from '../../providers/db/db';
import { FirebaseDbProvider } from '../../providers/firebase-db/firebase-db';


/**
 * Generated class for the ModalNuevoSitioPage page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */

@IonicPage()
@Component({
  selector: 'page-modal-nuevo-sitio',
  templateUrl: 'modal-nuevo-sitio.html',
})
export class ModalNuevoSitioPage {

  coords : any = { lat: 0, lng: 0 }
  address: string;
  description: string = '';
  foto: any = '';

  constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
    private viewCtrl : ViewController,
    private camera: Camera,
    // private db: DbProvider
    private dbFirebase :FirebaseDbProvider,
  ) {

  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad ModalNuevoSitioPage');
      this.coords.lat = this.navParams.get('lat');
      this.coords.lng = this.navParams.get('lng');
       this.getAddress(this.coords).then(results=> {
        this.address = results[0]['formatted_address'];
      }, errStatus => {
          // Aquí iría el código para manejar el error
      });
  }

  cerrarModal(){
    this.viewCtrl.dismiss();
  }

  getAddress(coords):any {
    var geocoder = new google.maps.Geocoder();

    return new Promise(function(resolve, reject) {
        geocoder.geocode({'location': coords} , function (results, status) { // llamado asincronamente
            if (status == google.maps.GeocoderStatus.OK) {
                resolve(results);
            } else {
                reject(status);
            }
        });
    });
  }

  sacarFoto(){

    let cameraOptions : CameraOptions = {
        quality: 50,
        encodingType: this.camera.EncodingType.JPEG,
        targetWidth: 800,
        targetHeight: 600,
        destinationType: this.camera.DestinationType.DATA_URL,
        sourceType: this.camera.PictureSourceType.CAMERA,
        correctOrientation: true
    }


    this.camera.getPicture(cameraOptions).then((imageData) => {
      // imageData is a base64 encoded string
        this.foto = "data:image/jpeg;base64," + imageData;
    }, (err) => {
        console.log(err);
    });
  }

  guardarSitio(){
    let sitio = {
      lat: this.coords.lat,
      lng: this.coords.lng ,
      address: this.address,
      description: this.description,
      foto: this.foto
    }
  
this.dbFirebase.guardaSitio(sitio).then(res=>{
        console.log('Sitio guardado en firebase:');
        this.cerrarModal();
    })
   }
}

Como ahora vamos a guardar nuestros sitios en firebase he comentado el import de DbProvider ya que ahora no lo vamos a utilizar, y en la función guardarSitio hemos sustituido la linea this.db.addSitio… por this.dbFirebase.guardaSitio…

Como vemos llamamos a la función guardaSitio del provider FirebaseDbProvider que hemos creado más arriba y le pasamos como parámetro el objeto sitio con los datos de nuestro sitio.

Ahora ejecutamos nuestra aplicación y vamos a la consola de firebase, antes de guardar ningún sitio si seleccionamos Database en el menú de la izquierda de la consola de firebase veremos algo como esto:

Ahora vamos a guardar un nuevo sitio desde nuestra app, rellenamos los campos del formulario del modal nuevo sitio y le damos a guardar, si todo ha ido bien ahora en la consola de firebase veremos algo como esto (pulsa en el icono ‘+’ para desplegar los campos):

Como vemos los datos de guardan en una estructura de árbol, en el primer nivel esta sitio, de sitio ‘cuelga’ los id de usuario, en este caso solo tenemos el nuestro pero si distribuís la aplicación por cada usuario habrá un nodo, de cada usuario ‘cuelgan’ los ids de cada sitio, y de cada id a su vez cuelgan los campos del sitio.

Ahora que ya podemos guardar nuestros sitios en firebase vamos a ver como podemos obtener todos los sitios que tenemos guardados para mostrarlos en el listado:

Lo primero que vamos ha hacer es crear una función en firebase-db.ts para obtener el listado de sitios guardados en firebase database:

getSitios(){
    return this.afDB.list('sitios/'+this.auth.getUser()).valueChanges();
  }

Para obtener el listado de sitios guardados utilizamos el método list de AngularFireDatabase pasando como parámetro la ruta a partir de la cual queremos obtener los datos, en este caso queremos obtener todo lo que cuelgue de sitios/id_usuario,  es decir todos los sitios de nuestro usuario, el id de usuario una vez más lo obtenemos con this.auth.getUser() que hemos creado en nuestro provider AuthProvider.

Con .valueChanges() devolvemos un observable cuando se produzcan cambios en la base de datos.

Bien, llegados a este punto vamos a modificar el controlador de la página listado para que en lugar de obtener los datos de la base de datos local los obtenga directamente de firebase.

Editamos listado.ts e importamos FirebaseDbProvider:

import { Component } from '@angular/core';
import { AlertController, IonicPage, NavController, NavParams, ModalController } from 'ionic-angular';
import { DbProvider } from '../../providers/db/db';
import { FirebaseDbProvider } from '../../providers/firebase-db/firebase-db';


/**
 * Generated class for the ListadoPage page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */

@IonicPage()
@Component({
  selector: 'page-listado',
  templateUrl: 'listado.html',
})
export class ListadoPage {

  sitios: any;

  constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
    public db : DbProvider,
    public modalCtrl : ModalController,
    public alertCtrl : AlertController,
    public dbFirebase :FirebaseDbProvider,

  ) {

  }

Ahora en la función ionViewDidEnter() vamos a sustituir this.db.getSitios().then((res)=>{…}) por lo siguiente:

ionViewDidEnter(){
   
    this.dbFirebase.getSitios().subscribe(sitios=>{
      this.sitios = sitios;
    })

}

La función getSitios() que como hemos visto a su vez llama al método list de AngularFireDatabase nos devuelve un observable, por lo que nos suscribimos al resultado. Aquí podemos ver claramente la diferencia entre un observable y una promesa.

Guardamos en this.sitios la lista de sitios que obtenemos lo que hará que se refresque automáticamente en la vista del listado.

Como ya sabemos una promesa ejecuta lo que tengamos definido en  then(res=>{ …}) una vez tenga listo el resultado, pero esto se ejecuta una única vez, sin embargo un observable va a ejecutar lo que tengamos definido en .subscribe(res=>{…}) cada vez que haya un cambio en el resultado. Por ejemplo si desde la consola de firebase cambiamos a mano el campo description de nuestro sitio, este se verá automáticamente reflejado en nuestra aplicación, es interesante hacer la prueba.

En este momento ya podemos guardar sitios en firebase y mostrarlos en el listado:

Lo siguiente que vamos ha hacer es la modificación de los sitios guardados.

Para ello primero vamos ha hacer una pequeña modificación a la función guardaSitio() de nuestro provider firebase-db.ts:

guardaSitio(sitio){
   if(!sitio.id){
      sitio.id  = Date.now();
    }
    return this.afDB.database.ref('sitios/'+this.auth.getUser()+'/'+sitio.id).set(sitio)
  }

Hemos añadido un if para comprobar si el sitio que recibimos en la función tiene el campo id definido.

Como utilizamos el id del sitio para establecer la ruta del registro, si el sitio que recibimos para guardar no tiene id significa que es un sitio nuevo y entonces le damos un id, si ya tiene un id significa que es un sitio que ya existe y hay que modificar.

Modificar un registro en firebase se hace exactamente igual que crear uno nuevo, si la ruta a la que hacemos referencia no existe crea el registro, si ya existe entonces modifica el registro existente en firebase.

Ahora vamos a  modificar el archivo modal-detalle-sitio.ts para hacer que al guardar los cambios al editar un sitio existente se guarden los cambios en firebase, para ello vamos a importar FirebaseDbProvider para poder llamar a la función guardaSitio que acabamos de modificar, y vamos también a modificar la función guardarCambios para que guarde los cambios en firebase en lugar de en la base de datos local:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';
import { LaunchNavigator } from '@ionic-native/launch-navigator';
import { Camera, CameraOptions } from '@ionic-native/camera';
// import { DbProvider } from '../../providers/db/db';
import { FirebaseDbProvider } from '../../providers/firebase-db/firebase-db';

/**
 * Generated class for the ModalDetalleSitioPage page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */

@IonicPage()
@Component({
  selector: 'page-modal-detalle-sitio',
  templateUrl: 'modal-detalle-sitio.html',
})
export class ModalDetalleSitioPage {

  sitio: any;
  edit : boolean = false;

  constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
    private viewCtrl : ViewController,
    private launchNavigator : LaunchNavigator,
    private camera: Camera,
  //  private db: DbProvider,
    private dbFirebase :FirebaseDbProvider

  ) {
     this.sitio = this.navParams.data;
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad ModalDetalleSitioPage');
  }

  cerrarModal(){
    this.viewCtrl.dismiss();
  }

  comoLlegar(){
    let destino = this.sitio.lat+', '+this.sitio.lng;
    this.launchNavigator.navigate(destino)
    .then(
      success => console.log('Launched navigator'),
      error => console.log('Error launching navigator', error)
    );
 }

 editar(){
   this.edit = true;
 }

 sacarFoto(){

    let cameraOptions : CameraOptions = {
        quality: 50,
        encodingType: this.camera.EncodingType.JPEG,
        targetWidth: 800,
        targetHeight: 600,
        destinationType: this.camera.DestinationType.DATA_URL,
        sourceType: this.camera.PictureSourceType.CAMERA,
        correctOrientation: true
    }


    this.camera.getPicture(cameraOptions).then((imageData) => {
      // imageData is a base64 encoded string
        this.sitio.foto = "data:image/jpeg;base64," + imageData;
    }, (err) => {
        console.log(err);
    });
  }

  guardarCambios(){

     let sitio = {
      id : this.sitio.id,
      lat: this.sitio.lat,
      lng: this.sitio.lng ,
      address: this.sitio.address,
      description: this.sitio.description,
      foto: this.sitio.foto
    }

    this.dbFirebase.guardaSitio(sitio).then(res=>{
        console.log('Sitio modificado en firebase');
        this.cerrarModal();
    })
   }

}

Hemos comentado el import de DbProvider porque ya no lo estamos utilizando.

Ahora si pruebas la aplicación con el panel de firebase abierto podrás ver que si modificas un sitio que tengas guardado automáticamente se verá reflejado este cambio en el registro de firebase.

Para concluir solo nos queda eliminar sitios.

Vamos a añadir a  FirebaseDbProvider en el archivo firebase-db.ts una función para eliminar un sitio de la base de datos de firebase:

public borrarSitio(id){
        this.afDB.database.ref('sitios/'+this.auth.getUser()+'/'+id).remove();

}

Como puedes ver es muy sencillo, solo necesitamos recibir el id del sitio que queremos eliminar y haciendo referencia a la ruta de nuestro sitio (que una vez más es sitios/_id_usuario_/id_sitio) utilizamos la función remove() para eliminar el sitio.

Ahora en el listado solo tenemos que sustituir la llamada a borrarSitio() de la base de datos local  de DbProvider por la función que acabamos de crear en FirebaseDbProvider, por lo tanto editamos el archivo listado.ts y dejamos la función borrarSitio() de la siguiente manera:

borrarSitio(id){

    let alert = this.alertCtrl.create({
      title: 'Confirmar borrado',
      message: '¿Estás seguro de que deseas eliminar este sitio?',
      buttons: [
        {
          text: 'No',
          role: 'cancel',
          handler: () => {
            // Ha respondido que no así que no hacemos nada
          }
        },
        {
          text: 'Si',
          handler: () => {
               // AquÍ borramos el sitio en firebase
              this.dbFirebase.borrarSitio(id);
           }
        }
      ]
    });

    alert.present();

 }

Como podemos observar ya no necesitamos obtener de nuevo los sitios una vez borrado para que se refresque el listado ya que al ser firebase una base de datos en tiempo real el listado se actualiza automáticamente.

Podéis probar a borrar un sitio y veréis como el sitio se elimina automáticamente en el panel de firebase y el listado se actualiza.

Eso es todo por hoy, con esto ya podemos hacer muchas cosas interesantes utilizando firebase, seguro que se os ocurren grandes ideas para realizar apps utilizando firebase como backend. Podéis dejarme en los comentarios  esas grandes ideas, no se lo contaré a nadie ;-P

 

Si necesitas desarrollar una aplicación móvil no dudes en solicitarme un presupuesto sin compromiso:

Tutorial de Ionic – Peticiones http – API REST

En el siguiente enlace tienes el índice para acceder al resto de entradas de este tutorial:

 

¡¡Atención!! este tutorial se basa en ionic 3 y está desactualizado por lo que es posible que los ejemplos no funcionen en la última versión de ionic, haz click aquí para acceder a un tutorial mas actual de Ionic.

 

Hola a todos,  hoy vamos a ver como podemos comunicar una aplicación desarrollada con ionic con una API REST, para ello vamos a aprender como realizar peticiones a un servidor remoto a través de http.

En esta pequeña prueba vamos a ver como podemos acceder a una API REST para obtener desde un servidor remoto un listado de usuarios. Para este pequeño ejemplo vamos a utilizar RANDOM USER GENERATOR que como se indica en su web es una API libre de código abierto para generar datos de usuario aleatorios para realizar pruebas. Como Lorem Ipsum, pero con personas.

Lo que vamos a hacer es simplemente realizar una llamada a esta API donde recibiremos como respuesta un listado de usuarios que mostraremos en nuestra vista.

Antes de nada vamos a crear una nueva aplicación de prueba:

ionic start pruebahttp1 blank

Ahora vamos a crear un provider donde gestionaremos la comunicación con el servidor remoto:

ionic g provider http

Se habrá creado una carpeta providers (si no existía) y dentro una carpeta con el nombre del provider que acabamos de crear (http) y dentro un archivo .ts con el mismo nombre.

Por defecto contendrá el siguiente código:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

/*
  Generated class for the HttpProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class HttpProvider {

  constructor(public http: HttpClient) {
    console.log('Hello HttpProvider Provider');
  }

}

Vemos que al crear un provider ya se importa el módulo HttpClient.

Para poder utilizar HttpClient tenemos que importar y declarar HttpClientModule en el archivo app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { HttpProvider } from '../providers/http/http';
import { HttpClientModule } from '@angular/common/http';



@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    HttpClientModule
    
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    HttpProvider
  ]
})
export class AppModule {}

http.get

Ahora vamos a añadir un método que llamaremos loadUsers a nuestro provider para obtener la lista de usuarios desde el servidor, por lo tanto editamos el archivo http.ts y añadimos el siguiente código:
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

/*
  Generated class for the HttpProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class HttpProvider {

  path : string = 'https://randomuser.me/api/?results=25';
  
  constructor(public http: HttpClient) {
    console.log('Hello HttpProvider Provider');
  }

  loadUsers(){
    return this.http
    .get(this.path)
  }
}
http.get devuelve el resultado de la solicitud en la forma de un observable.
El listado de usuarios tendrá un formato similar a este:
{
  "results": [
    {
      "gender": "male",
      "name": {
        "title": "mr",
        "first": "romain",
        "last": "hoogmoed"
      },
      "location": {
        "street": "1861 jan pieterszoon coenstraat",
        "city": "maasdriel",
        "state": "zeeland",
        "postcode": 69217
      },
      "email": "romain.hoogmoed@example.com",
      "login": {
        "username": "lazyduck408",
        "password": "jokers",
        "salt": "UGtRFz4N",
        "md5": "6d83a8c084731ee73eb5f9398b923183",
        "sha1": "cb21097d8c430f2716538e365447910d90476f6e",
        "sha256": "5a9b09c86195b8d8b01ee219d7d9794e2abb6641a2351850c49c309f1fc204a0"
      },
      "dob": "1983-07-14 07:29:45",
      "registered": "2010-09-24 02:10:42",
      "phone": "(656)-976-4980",
      "cell": "(065)-247-9303",
      "id": {
        "name": "BSN",
        "value": "04242023"
      },
      "picture": {
        "large": "https://randomuser.me/api/portraits/men/83.jpg",
        "medium": "https://randomuser.me/api/portraits/med/men/83.jpg",
        "thumbnail": "https://randomuser.me/api/portraits/thumb/men/83.jpg"
      },
      "nat": "NL"
    }
  ],
  "info": {
    "seed": "2da87e9305069f1d",
    "results": 1,
    "page": 1,
    "version": "1.1"
  }
}
Ahora vamos a crear la vista en home.html para mostrar un botón que llamará a la función
cargarUsuarios, y un listado de items con los usuarios que crearemos recorriendo con *ngFor el array usuarios que posteriormente vamos a crear en el controlador:
<ion-header>
  <ion-navbar>
    <ion-title>
      Usuarios
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>
  <ion-list>
    <ion-item *ngFor="let usuario of usuarios">
      <ion-avatar item-start>
        <img [src]="usuario.picture.medium">
      </ion-avatar>
      <h2>{{ usuario.name.first }}</h2>
      <p>{{ usuario.email }}</p>
    </ion-item>
  </ion-list>
  <button ion-button full (click) = "cargarUsuarios()">Cargar Usuarios</button>
</ion-content>
Ahora vamos a modificar home.ts para obtener los datos desde el provider y mostrarlos en la vista.
Editamos home.ts e importamos el provider httpProvider que acabamos de crear:
import { HttpProvider } from '../../providers/http/http';
Para poder utilizarlo debemos inyectarlo en el constructor:
constructor(public navCtrl: NavController, public http: HttpProvider) {

  }
Justo antes del constructor vamos a definir una variable miembro donde guardaremos el array de usuarios que recibamos desde el servidor:
usuarios : any[];

Promesas y Observables

Ahora vamos a crear un método que que a su vez llamará al método loadUsers de nuestro provider para recibir los datos de los usuarios:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { HttpProvider } from '../../providers/http/http';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  usuarios : any[];

  constructor(public navCtrl: NavController, public http: HttpProvider) {

  }

  cargarUsuarios(){
    this.http.loadUsers().subscribe(
      (res) => { 
        this.usuarios = res['results'];
      },
      (error) =>{
        console.error(error);
      }
    )
  }


}

Como podemos observar llamamos al método loadUsers que hemos definido en el provider, pero no utilizamos then sino subscribe, esto es porque http no devuelve una promesa si no que devuelve un observable. Un observable se queda a la espera de recibir datos y nosotros nos “subscribimos” recibiendo estos datos en cuanto estén disponibles.
Esta cualidad se puede utilizar para suscribirnos a la url y observar si ha habido cambios, como por ejemplo si es un sitio de noticias donde se están continuamente renovando.

La diferencia entre promesas y observables a groso modo es que la promesa devuelve los datos una única vez cuando estos son recibidos mientras que un observable se queda “vigilando” si se han producido cambios y se ejecuta cada vez que un cambio se produce, aunque hasta que no te suscribes a un observable éste no se ejecutará.

Si solo necesitamos recibir los datos una única vez sin necesidad de observar si se han producido cambios podemos utilizar promesas. Una promesa se ejecuta una vez que se haya resuelto la llamada y recibimos los datos en la función .then(res=>{… }).

El código del provider en el archivo http.ts utilizando una promesa quedaría de la siguiente manera:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import 'rxjs/add/operator/toPromise';

/*
  Generated class for the HttpProvider provider.

  See https://angular.io/guide/dependency-injection for more info on providers
  and Angular DI.
*/
@Injectable()
export class HttpProvider {

  path : string = 'https://randomuser.me/api/?results=25';
  
  constructor(public http: HttpClient) {
    console.log('Hello HttpProvider Provider');
  }

  loadUsers(){
    return this.http
    .get(this.path)
    .toPromise();

  }  
}

Debemos importar el operador toPromise para convertir el observable en promesa  con import ‘rxjs/add/operator/toPromise’;  y simplemente añadir toPromise() después del get.

Ahora en home.ts solo tenemos que sustituir subscribe por then:

cargarUsuarios(){
  this.http.loadUsers().then(
    (res) => { 
      this.usuarios = res['results'];
    },
    (error) =>{
      console.error(error);
    }
  )
}

Si ejecutáis la aplicación con ionic serve -l y pulsáis en el botón Cargar Usuarios podréis ver algo como esto:

Listado de usuarios
Listado de usuarios

Podéis observar como cada vez que pulséis el botón la lista de usuarios cambia ya que Random User Generator como su propio nombre indica devuelve una lista aleatoria de usuarios.

http.post

RANDOM USER GENERATOR  nos ha servido para aprender ha hacer una petición get a una API REST para recibir datos, en este caso una lista de usuarios.

Si queréis conectaros con un servicio que tengáis corriendo en un servidor remoto y necesitáis pasarle datos desde la aplicación para que se guarden en el servidor tenemos que usar http.post.

Vamos a imaginar que tenemos corriendo un servicio en PHP cuya url sea www.miservicio.com/adduser/ que está programado para recibir vía post un nuevo usuario para guardarlo en la base de datos del servidor.

Tendremos que crear  una función similar a esta:

postDatos(){
  let datos = { nombre:'Edu',email:'edu.revilla.vaquero@gmail.com'}
  
  let options = {
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  };
 var url = 'www.miservicio.com/adduser/';
 return new Promise(resolve => {
  this.http.post(url,JSON.stringify(datos),options)
     .subscribe(data => {
       resolve(data);
      });
 });
}

En la variable datos  tenemos un objeto con los datos del usuario que queremos enviar al servidor, en este caso nombre y email.

Después definimos la variable options donde a su vez definimos las cabeceras en la variable headers, dependiendo de la configuración del servidor podría necesitar parámetros diferentes en la cabecera.

Como vemos http.post es bastante parecido a http.get, solo que como segundo parámetro le pasamos la variable que contiene los datos que queremos enviar convertida a formato JSON con JSON.stringify(datos), como tercer parámetro le pasamos options.

En data recibiremos la respuesta que nos de el servidor.

Vamos a ver un ejemplo de como obtendríamos los datos enviados desde nuestra aplicación con un servicio desarrollado en PHP en el servidor:

<?php
$postdata = file_get_contents("php://input");
if (isset($postdata)) {
$request = json_decode($postdata);
echo $request->nombre;
echo $request->email;
}
?>

Evidentemente en otros lenguajes de programación del lado del servidor el código para recibir los datos sería diferente, pero se escapa del propósito de este tutorial el abordar como sería en cada leguaje. Si alguno hace la prueba en otro lenguaje y quiere compartirlo en los comentarios para ayudar a los demás será bien recibido :-).

Eso es todo por hoy.

Si necesitas desarrollar una aplicación móvil no dudes en solicitarme un presupuesto sin compromiso:

Tutorial de Ionic – Crear una aplicación para guardar nuestros sitios geolocalizados – Parte 8 – Eliminar un sitio desde el listado deslizando el item con "ItemSliding".

En el siguiente enlace tienes el índice para acceder al resto de entradas de este tutorial:

 

¡¡Atención!! este tutorial se basa en ionic 3 y está desactualizado por lo que es posible que los ejemplos no funcionen en la última versión de ionic, haz click aquí para acceder a un tutorial mas actual de Ionic.

Hola a todos.

En el post anterior  vimos como modificar un sitio, hoy vamos a mejorar nuestra app para que se puedan eliminar nuestros sitio, pero para aprender más sobre las posibilidades de ionic lo vamos ha hacer desde el listado, deslizando el elemento del listado hacia la izquierda nos mostrará el botón de eliminar. También aprovecharemos para aprender a utilizar AlertController para mostrar un dialogo que pida confirmación antes de eliminar el sitio.

Vamos a ver como sería:

Lo primero que vamos a hacer es modificar el listado para añadir un ion-item-sliding que es un componente que nos permite deslizar el item, debemos introducir el item dentro de ion-item-sliding y le vamos a añadir despues del item un elemento ion-item-options que nos permite añadir opciones que se mostrarán al deslizar el item. Se pueden añadir tantas opciones como desees, nosotros solo vamos a necesitar una para borrar el elemento. Vamos a ver como tiene que quedar el código, editamos el archivo listado.html y lo dejamos de la siguiente manera:

<!--
  Generated template for the ListadoPage page.

  See http://ionicframework.com/docs/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>Listado</ion-title>
  </ion-navbar>

</ion-header>


<ion-content padding>
<ion-list>
  <ion-item-sliding #item *ngFor="let sitio of sitios">
    <ion-item (click)="muestraSitio(sitio)">
      <ion-thumbnail item-left>
        <img [src]="sitio.foto">
      </ion-thumbnail>
      <h2>{{ sitio.address }}</h2>
      <p>{{ sitio.description }}</p>
    </ion-item>
    <ion-item-options side="right">
      <button ion-button  color="danger" (click)="borrarSitio(sitio.id)"><ion-icon name="trash"></ion-icon>
         Borrar
      </button>
    </ion-item-options>
  </ion-item-sliding>
</ion-list>
</ion-content>

Como podemos observar hemos puesto el *ngFor en la etiqueta ion-item-sliding que ahora envuelve al ion-item que ya teníamos.

Despues del ion-item hemos añadido un componente ion-item-options al que le indicamos que se muestre en el lado derecho (side=”right”).

Dentro de ion-item-options tenemos un botón con in icono (ion-icon) que muestra el icono “trash” que es el típico cubo de basura y el texto “borrar”.
En el evento click del botón le decimos que llame a la función borrarSitio.

Si probamos la aplicación podéis comprobar en el listado que los items ahora se pueden arrastrar hacia la izquierda y aparece el botón de borrar:

Borrando un sitio
Borrando un sitio

Ahora debemos definir la función borrar sitio en el controlador del listado. Antes de borrar vamos a sacar un dialogo de confirmación que nos preguntará si realmente queremos eliminar el sitio, para ello vamos a utilizar el componente Alert.

Alerts

Los alerts son una excelente manera de ofrecer al usuario la posibilidad de elegir una acción específica o una lista de acciones. También pueden proporcionar al usuario información importante, o requerir que tomen una decisión.

Ionic nos proporciona los siguientes tipos de Alerts:

En este caso  como lo que queremos es que el usuario confirme el borrado del sitio vamos a utilizar alert de confirmación.

Para poder utilizar un alert lo primero que debemos hacer es importar AlertController e inyectarlo en el constructor, por lo tanto vamos a editar el archivo listado.ts y añadimos el siguiente código:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ModalController } from 'ionic-angular';
import { DbProvider } from '../../providers/db/db';
import { AlertController } from 'ionic-angular';


/**
 * Generated class for the ListadoPage page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */
@IonicPage()
@Component({
  selector: 'page-listado',
  templateUrl: 'listado.html',
})
export class ListadoPage {

  sitios: any;
  constructor(public navCtrl: NavController,
              public navParams: NavParams,
              public modalCtrl : ModalController,
              public db : DbProvider,
              public alertCtrl : AlertController) {
  }

....

Ahora  vamos a  a crear la función borrarSitio() y definiremos dentro de ella el alert de la siguiente manera:

borrarSitio(id){

    let alert = this.alertCtrl.create({
      title: 'Confirmar borrado',
      message: '¿Estás seguro de que deseas eliminar este sitio?',
      buttons: [
        {
          text: 'No',
          role: 'cancel',
          handler: () => {
            // Ha respondido que no así que no hacemos nada
          }
        },
        {
          text: 'Si',
          handler: () => {
               // AquÍ borramos el sitio en la base de datos

           }
        }
      ]
    });

    alert.present();

 }

Para crear un alert utilizamos el método create donde tenemos que definir el título en este caso “Confirmar borrado”, el mensaje: “¿Estas seguro de que deseas eliminar este sitio?”.

Después definimos los dos botones de acción uno para SI y otro para NO, en text definimos el texto del botón y en handler definiremos la acción que debemos realizar, en caso de pulsar “No” no hacemos nada, en caso de pulsar “Si” procederemos a eliminar el sitio.

Una vez definido el alert con alert.present() hacemos que se muestre.

Ahora que ya sabemos como se usan los alerts vamos a escribir el código del botón “Si” para borrar el sitio.

Lo primero que tenemos que hacer es crear un nuevo método en el provider DbProvider para eliminar los sitios, por lo tanto editamos el archivo db.ts y añadimos la función borrarSitio():

public borrarSitio(id){
     let sql = "DELETE FROM sitios WHERE id= ? ";
     return this.db.executeSql(sql,[id]);
  }

Recibimos como parámetro id que es el id del sitio que queremos eliminar de la base de datos, ejecutamos la query y si todo ha ido bien e sitio se habrá eliminado para siempre de la base de datos.

Ahora que ya tenemos preparada la función para borrar el sitio en el provider vamos a volver al alert de confirmación en listado.ts para definir las operaciones a realizar al pulsar el botón “Si”, en la función handler del botón “Si” añadimos el siguiente código:

borrarSitio(id){

    let alert = this.alertCtrl.create({
      title: 'Confirmar borrado',
      message: '¿Estás seguro de que deseas eliminar este sitio?',
      buttons: [
        {
          text: 'No',
          role: 'cancel',
          handler: () => {
              // Ha respondido que no así que no hacemos nada         
           }
        },
        {
          text: 'Si',
          handler: () => {

              this.db.borrarSitio(id).then((res)=>{
            // Una vez borrado el sitio recargamos el listado
              this.db.getSitios().then((res)=>{
              this.sitios = [];
              for(var i = 0; i < res.rows.length; i++){
                  this.sitios.push({
                    id : res.rows.item(i).id,
                    lat: res.rows.item(i).lat,
                    lng: res.rows.item(i).lng,
                    address: res.rows.item(i).address,
                    description: res.rows.item(i).description,
                    foto: res.rows.item(i).foto
                  });
              }

              },(err)=>{ /* alert('error al sacar de la bd'+err) */ })

            },(err)=>{ /* alert('error al borrar de la bd'+err) */ });
          }
        }
      ]
    });

    alert.present();

   }

Lo primero que hacemos es llamar al método que acabamos de crear en el provider con this.db.borrarSitio(id) pasándole como parámetro el id del sitio.

En el then lo que vamos ha hacer es refrescar el listado para ello debemos modificar la variable miembro this.sitios que contiene los sitios que se muestran en el listado, lo que hacemos es volver a cargar de la base de datos los sitios, como acabamos de borrar un sitio este no aparecerá. Para cargar los sitios de la base de datos utilizamos el método this.db.getSitios que ya tenemos definido y hacemos exactamente el mismo proceso que en la función  ionViewDidEnter para rellenar el array this.sitios con los datos obtenidos desde la base de datos. De hecho podemos definir una función que se encargue de recoger los sitios de la base de datos y rellenar el array this.sitios en lugar de repetir el código de ionViewDidEnter, eso ya lo dejo como mejora opcional.

Si ejecutáis el código comprobaréis que ya podéis borrar los sitio que no os interesen conservar en vuestro dispositivo.

Eso es todo por hoy.

 

Si necesitas desarrollar una aplicación móvil no dudes en solicitarme un presupuesto sin compromiso:

Tutorial de Ionic – Crear una aplicación para guardar nuestros sitios geolocalizados – Parte 6 – Mostrar detalles del sitio guardado y abrir navegador gps para llegar a el.

En el siguiente enlace tienes el índice para acceder al resto de entradas de este tutorial:

 

¡¡Atención!! este tutorial se basa en ionic 3 y está desactualizado por lo que es posible que los ejemplos no funcionen en la última versión de ionic, haz click aquí para acceder a un tutorial mas actual de Ionic.

Hola a todos, en el post anterior vimos como guardar nuestros sitios en la base de datos y como mostrar los sitios que tenemos guardados en la página listado.

Hoy vamos a mostrar el detalle del sitio al pulsar sobre el en el listado. La idea es que nos muestre la dirección, la foto y la descripción que hayamos introducido, además vamos a añadir un botón de “Como llegar” para que nos abra el navegador gps del móvil con la dirección hacía dicho sitio. Ya no volveremos a olvidar donde hemos aparcado el coche, o podremos recordar como volver a ese restaurante que tanto nos gustó.

Lo primero que tenemos que hacer es crear un nuevo modal con ionic generator:

ionic g page modalDetalleSitio

Ahora en listado.ts debemos importar el componente ModalController  de la librería ionic-angular, por lo tanto después de NavController y NavParams importamos también ModalController.

import { IonicPage, NavController, NavParams, ModalController } from 'ionic-angular';

Debemos inyectar también en el constructor el componente ModalController al que hemos llamado modalCtrl:

constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
    public modalCtrl : ModalController,
    public db: DbProvider) { ......

Ahora ya podemos crear un método en listado.ts para abrir el modal que acabamos de crear pasandole como parámetro el sitio que queremos mostrar:

muestraSitio(sitio){
      let modalSitio = this.modalCtrl.create( 'ModalDetalleSitioPage', sitio );
      modalSitio.present();
   }

Vamos a modificar el listado para que al pulsar sobre un sitio de la lista se abra el modal por lo tanto editamos listado.html y añadimos lo siguiente:

<!--
  Generated template for the Listado page.

  See http://ionicframework.com/docs/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>Listado</ion-title>
  </ion-navbar>

</ion-header>


<ion-content padding>
<ion-list>
  <ion-item *ngFor="let sitio of sitios" (click)="muestraSitio(sitio)">
    <ion-thumbnail item-left>
      <img [src]="sitio.foto">
    </ion-thumbnail>
    <h2>{{ sitio.address }}</h2>
    <p>{{ sitio.description }}</p>
  </ion-item>
</ion-list>
</ion-content>

Con esto llamamos al método muestraSitio que acabamos de definir en el controlador listado.ts y le pasamos como parámetro el sitio sobre el se ha hecho click.

Ahora vamos a editar el archivo modal-detalle-sitio.ts y vamos a importar e inyectar en en constructor el elemento ViewController que lo utilizaremos para poder cerrar el modal:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';

/**
 * Generated class for the ModalDetalleSitio page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */

@IonicPage()
@Component({
  selector: 'page-modal-detalle-sitio',
  templateUrl: 'modal-detalle-sitio.html',
})
export class ModalDetalleSitioPage {

  constructor(public navCtrl: NavController, public navParams: NavParams, private viewCtrl : ViewController) {
}

...

Vamos a crear también una función para cerrar el modal a la que luego llamaremos desde el botón de cerrar de la vista:

cerrarModal(){
    this.viewCtrl.dismiss();
  }

Ahora vamos a crear una variable miembro llamada sitio donde vamos a guardar el objeto con los datos del sitio que recibimos desde NavParams como parámetro al abrir el modal desde la página listado:

...
export class ModalDetalleSitioPage {

  sitio: any;
  constructor(public navCtrl: NavController, public navParams: NavParams, private viewCtrl : ViewController) {
      this.sitio = this.navParams.data;
}

...

Ahora ya estamos listos para poder mostrar los datos del sitio el la vista, así que editamos el archivo modal-detalle-sitio.html e introducimos el siguiente código:

<!--
  Generated template for the ModalDetalleSitio page.

  See http://ionicframework.com/docs/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>{{ sitio.address }}</ion-title>
     <ion-buttons start>
      <button ion-button (click)="cerrarModal()">
        <ion-icon name="md-close"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>

</ion-header>


<ion-content padding>
  <ion-card>
    <img [src]="sitio.foto" *ngIf="sitio.foto" />
    <ion-card-content>
       <ion-item text-wrap>
          <h2>{{ sitio.address }}</h2>
           <p>{{ sitio.description }}</p>
        </ion-item>
    </ion-card-content>
    <ion-row>
      <ion-col text-center>
         <button ion-button icon-left clear small (click)="comoLlegar()">
           <ion-icon name="navigate"></ion-icon>
           <div>Como llegar</div>
         </button>
      </ion-col>
    </ion-row>
</ion-card>
</ion-content>

Como título del modal hemos puesto la dirección que está almacenada en sitio.address.

Después añadimos el botón para cerrar el modal, para ello le indicamos que en el evento click llame a la función cerrarModal que hemos definido en el controlador.

Después dentro de ion-content  creamos un elemento ion-card y dentro mostramos la foto solo si existe con el condicional *ngIf.

Después dentro de ion-card-content  mostramos la dirección y la descripción.

Por último creamos un botón con un icono de tipo navigate dentro de un elemento ion-col que a su vez esta dentro de un elemento ion-row. Hacemos esto para conseguir que el botón se muestre centrado gracias a la propiedad text-center que hemos añadido a la etiqueta ion-col.

Para saber más sobre los componentes disponibles en ionic podéis una vez más leer la documentación oficial de ionic: https://ionicframework.com/docs/components/

Como podemos observar en el evento click del botón llamamos a la función comoLlegar().

La idea es que cuando pulsemos en el botón nos habrá el navegador gps que tengamos instalado por defecto en el móvil para indicarnos la ruta a seguir para llegar al lugar.

Vamos  ello vamos a crear la función comoLlegar en el controlador.

Aunque podríamos utilizar la función que ya expliqué en esté post perteneciente a la serie de posts que publiqué hace tiempo sobre google maps, vamos a utilizar en su lugar el plugin LaunchNavigator que ya nos ofrece esta funcionalidad de forma nativa (gracias a writ3r por el aporte).

Lo primero que debemos hacer es instalar el plugin escribiendo los siguientes comandos desde consola:

ionic cordova plugin add uk.co.workingedge.phonegap.plugin.launchnavigator
npm install --save @ionic-native/launch-navigator

Ahora debemos importar y declarar el plugin en app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';

import { Geolocation } from '@ionic-native/geolocation';
import { Camera } from '@ionic-native/camera';
import { DbProvider } from '../providers/db/db';
import { SQLite } from '@ionic-native/sqlite';
import { LaunchNavigator } from '@ionic-native/launch-navigator';

@NgModule({
  declarations: [
    MyApp
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    Geolocation,
    Camera,
    DbProvider,
    SQLite,
    LaunchNavigator

  ]
})
export class AppModule {}

Ahora editamos  el archivo modal-detalle-sitio.ts, importamos el plugin LauchNavigator, lo inyectamos en el constructor  y añadimos el método comoLlegar:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';
import { LaunchNavigator } from '@ionic-native/launch-navigator';

/**
 * Generated class for the ModalDetalleSitioPage page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */
@IonicPage()
@Component({
  selector: 'page-modal-detalle-sitio',
  templateUrl: 'modal-detalle-sitio.html',
})
export class ModalDetalleSitioPage {

  sitio: any;

  constructor(public navCtrl: NavController, public navParams: NavParams, private viewCtrl : ViewController, private launchNavigator : LaunchNavigator) {
     this.sitio = this.navParams.data;
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad ModalDetalleSitioPage');
  }

  cerrarModal(){
    this.viewCtrl.dismiss();
  }

  comoLlegar(){
    let destino = this.sitio.lat+', '+this.sitio.lng;
    this.launchNavigator.navigate(destino)
    .then(
      success => console.log('Launched navigator'),
      error => console.log('Error launching navigator', error)
    );

 }
}

El método navigate de launchNavigator recibe como parámetro la dirección donde queremos llegar, en este caso las coordenadas separadas por una coma que tenemos en las variables this.sitio.lat y this.sitio.lng.

Si ejecutamos nuestra aplicación en en móvil obtendremos algo como esto al entrar en el detalle de un sitio de la lista:

Modal detalle del sitio
Modal detalle del sitio

Bien , por hoy lo dejamos aquí, ya podemos guardar nuestros sitios favoritos, o recordar donde hemos aparcado el coche por ejemplo, podemos mostrar los sitios que hemos guardado y podemos abrir el navegador gps para poder llegar asta el lugar.

Aunque se puede mejorar mucho esta aplicación, ya podemos decir que es funcional y nos ha servido para aprender muchos conceptos sobre ionic. Como siempre os animo a que experimentéis y tratéis de mejorar la app por vuestra cuenta ya que es una magnifica forma de aprender y mejorar.

En el próximo post seguiremos desarrollando la app y veremos como modificar un sitio guardado.

 

Si necesitas desarrollar una aplicación móvil no dudes en solicitarme un presupuesto sin compromiso:

Tutorial de Ionic – Crear una aplicación para guardar nuestros sitios geolocalizados – Parte 5 – Guardando nuestros sitios en una base de datos local

En el siguiente enlace tienes el índice para acceder al resto de entradas de este tutorial:

 

¡¡Atención!! este tutorial se basa en ionic 3 y está desactualizado por lo que es posible que los ejemplos no funcionen en la última versión de ionic, haz click aquí para acceder a un tutorial mas actual de Ionic.

Hola a todos:

En el post anterior obtuvimos la dirección a partir de las coordenadas, creamos el formulario para introducir la descripción del lugar y aprendimos a sacar fotografías con nuestro móvil, bien, todo esto nos vale de muy poco si cuando cerramos la aplicación perdemos toda esta información.

Para poder guardar nuestros sitios y que sigan allí para poder consultarlos siempre que queramos tenemos que almacenarlos en una base de datos local en el dispositivo.

Provider

Antes de nada vamos a introducir un concepto nuevo, para manejar los datos que introduciremos y extraeremos de la base de datos vamos a utilizar un provider.

Los providers son proveedores que se encargan del manejo de datos, bien extraídos de la base de datos, desde una API REST, etc.

Para crear un provider acudimos una vez más a ionic generator con el comando  ionic g provider.

Para crear un proveedor para gestionar nuestra base de datos de sitios escribimos desde consola:

ionic g provider db

Esto nos creará un archivo typescript con el nombre del proveedor que hemos creado, en este caso db.ts dentro de la carpeta providers.

Por supuesto podéis dar el nombre que deseéis al provider que acabamos que crear, no tiene por que ser  “db”.

Vamos a echar un vistazo al código que se ha generado por defecto en db.ts:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

/*
  Generated class for the Db provider.

  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular 2 DI.
*/
@Injectable()
export class Db {

  constructor(public http: Http) {
    console.log('Hello Db Provider');
  }

}

Por defecto al crear un provider importa Http ya que los providers pueden ser utilizados para obtener datos des una petición a un servicio mediante Http, sin embargo como lo vamos a utilizar para gestionar nuestra base de datos no lo necesitamos y podemos eliminarlo, también debemos eliminarlo del constructor. Tampoco vamos a necesitar el “import ‘rxjs/add/operator/map'”, por lo que podemos eliminarlo también.

Para crear una base de datos vamos a utilizar SQlite, así que lo primero que necesitamos es instalar el plugin escribiendo desde consola:

ionic cordova plugin add cordova-sqlite-storage
npm install --save @ionic-native/sqlite

Recuerda que si está utilizando Linux o Mac necesitarás utilizar sudo por delante.

Una vez instalado el plugin ya podemos importarlo  en nuestro provider desde ionic-native.
Para poder utilizarlo  inyectamos la dependencia SQLite en el constructor, además vamos a crear una variable miembro llamada db donde se guardará el manejador de la base de datos una vez establecida la conexión :

import { Injectable } from '@angular/core';
import { SQLite, SQLiteObject } from '@ionic-native/sqlite';

/*
  Generated class for the Db provider.

  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular 2 DI.
*/
@Injectable()
export class DbProvider {

  db : SQLiteObject = null;
  constructor(public sqlite: SQLite) {
    console.log('Hello Db Provider');
  }

...
Ahora vamos a crear en nuestro provider un método para crear/abrir la base de datos:
public openDb(){
      return this.sqlite.create({
          name: 'data.db',
          location: 'default' // el campo location es obligatorio
      })
      .then((db: SQLiteObject) => {
       this.db =db;
     })
  }

Como vemos utilizamos el método openDatabase al cual le pasamos como parámetro un objeto especificando el nombre y la localización.El campo location lo dejamos en ‘default’.

Este método crea la base de datos si no existe y abre la conexión.
Si todo ha ido bien resuelve la promesa pasandonos como parámetro el manejador de la base de datos que es de tipo SQLiteObject, se lo asignamos a this.db para poder utilizarlo en las funciones que vamos a crear.

Ahora vamos a crear una tabla con los campos que vamos a necesitar para guardar nuestros sitios con los campos que necesitamos:

public createTableSitios(){
    return this.db.executeSql("create table if not exists sitios( id INTEGER PRIMARY KEY AUTOINCREMENT, lat FLOAT, lng FLOAT, address TEXT, description TEXT, foto TEXT )",{})
  }

Si ya has trabajado antes con otras bases de datos como por ejemplo MySQL  no te costará trabajo entender como funciona SQLite ya que la sintaxis es muy similar aunque con algunas limitaciones.

En este caso nuestra tabla va a tener un campo id de tipo integer que sera la clave primaria, un campo lat de tipo float donde guardaremos la latitud de las coordenadas, un campo lng de tipo float donde guardaremos la longitud, un campo address de tipo text para guardar la dirección, un campo llamado description de tipo text donde guardaremos la descripción del sitio y por último un campo foto también de tipo text donde guardaremos la foto en formato base 64.

Para continuar vamos a crear un método para guardar nuestros sitios:

public addSitio(sitio){
    let sql = "INSERT INTO sitios (lat, lng, address, description, foto) values (?,?,?,?,?)";
    return this.db.executeSql(sql,[sitio.lat,sitio.lng,sitio.address,sitio.description,sitio.foto]);
  }

En la sql definimos el insert con los campos de la tabla que vamos a introducir y en los valores ponemos interrogaciones ‘?’,  luego en el método execute pasamos como primer parámetro la sql y como segundo un array con los valores que corresponden a los campos donde van las interrogaciones, es decir hay que poner el valor de los campos en el mismo orden. El valor de los campos lo recibimos como parámetro en el objeto sitio. Cuando posteriormente llamemos a la función addSitio le tendremos que pasar un objeto con todos los campos.

Para poder utilizar el provider  y SQLite debemos importar ambos en app.module.ts y declararlos en la sección providers, ionic generator nos ha añadido automáticamente nuestro provider DbProvider en app.module.ts así que solo tenemos que preocuparnos de importar y declarar Sqlite:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';

import { Geolocation } from '@ionic-native/geolocation';
import { Camera } from '@ionic-native/camera';
import { DbProvider } from '../providers/db/db';
import { SQLite } from '@ionic-native/sqlite';


@NgModule({
  declarations: [
    MyApp
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    Geolocation,
    Camera,
    DbProvider,
    SQLite
  ]
})
export class AppModule {}

Vamos a importar nuestro provider también en app.component.ts y vamos a abrir la base de datos y crear la tabla en platform.ready para asegurarnos de que el plugin SQlite ya se ha cargado antes de utilizarlo:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { DbProvider } from '../providers/db/db';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage:any = 'MisTabsPage';

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, public db: DbProvider) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.
      statusBar.styleDefault();
      splashScreen.hide();
        this.db.openDb()
       .then(() => this.db.createTableSitios())

    });
  }
}

Por último importamos el provider en la página donde vamos a utilizarlo, en este caso en modal-nuevo-sitio.ts, también debemos inyectarlo en el constructor :

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';
import { Camera, CameraOptions } from '@ionic-native/camera';
import { DbProvider } from '../../providers/db/db';



/**
 * Generated class for the ModalNuevoSitio page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */
@IonicPage()
@Component({
  selector: 'page-modal-nuevo-sitio',
  templateUrl: 'modal-nuevo-sitio.html',
})
export class ModalNuevoSitioPage {

  coords : any = { lat: 0, lng: 0 }
  address: string;
  description: string = '';
  foto: any = '';

  constructor(public navCtrl: NavController, public navParams: NavParams, private viewCtrl : ViewController, private camera: Camera,  private db: DbProvider) {
  }

...

Si recordamos el post anterior, en la vista  modal-nuevo-sitio.html, en el evento ngSubmit del formulario llamábamos a la función guardarSitio().
Vamos a definir esta función en el controlador del modal en el archivo modal-nuevo-sitio.ts:

guardarSitio(){
    let sitio = {
      lat: this.coords.lat,
      lng: this.coords.lng ,
      address: this.address,
      description: this.description,
      foto: this.foto
    }
    this.db.addSitio(sitio).then((res)=>{
      this.cerrarModal();
     /*  alert('se ha introducido correctamente en la bd'); */
    },(err)=>{ /* alert('error al meter en la bd'+err) */ })
}

Bien, como podemos observar en el código lo que hacemos es crear un objeto llamado sitio al que le asignamos los valores que tenemos recogidos.

Recuerda que el valor de la descripción  se refleja automáticamente el la variable this.description  que tenemos definida en el controlador al utilizar [(ngModel)], las coordenadas y la dirección ya las teníamos recogidas y la foto se le asigna a this.foto en el momento de sacarla.

Después llamamos al método addSitio que hemos definido en nuestro provider pasándole como parámetro el objeto sitio.

Si todo ha ido bien cerramos el modal.

Bien, en este punto ya podemos guardar nuestros sitios en la base de datos, ahora nos toca poder sacar y mostrar un listado de los sitios que hemos guardado.

Para ello lo primero que vamos a hacer es crear un nuevo método en nuestro provider para obtener los sitios que tenemos guardados en la base de datos, por lo tanto editamos el archivo db.ts y añadimos el método getSitios:

import { Injectable } from '@angular/core';
import { SQLite, SQLiteObject } from '@ionic-native/sqlite';

/*
  Generated class for the Db provider.

  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular 2 DI.
*/
@Injectable()
export class Db {

  db : SQLiteObject = null;
  constructor(public sqlite: SQLite) {
    console.log('Hello Db Provider');
  }

  public openDb(){
      return this.sqlite.create({
          name: 'data.db',
          location: 'default' // el campo location es obligatorio
      })
      .then((db: SQLiteObject) => {
       this.db =db;
     })
  }

  public createTableSitios(){
    return this.db.executeSql("create table if not exists sitios( id INTEGER PRIMARY KEY AUTOINCREMENT, lat FLOAT, lng FLOAT, address TEXT, description TEXT, foto TEXT )",{})
  }

  public addSitio(sitio){
    let sql = "INSERT INTO sitios (lat, lng, address, description, foto) values (?,?,?,?,?)";
    return this.db.executeSql(sql,[sitio.lat,sitio.lng,sitio.address,sitio.description,sitio.foto]);
  }

  public getSitios(){
    let sql = "SELECT * FROM sitios";
    return this.db.executeSql(sql,{});
  }

}

Ha llegado el momento de mostrar nuestros sitios en la página listado, así que editamos el archivo listado.ts y vamos a importar el provider DbProvider en el controlador de la página listado, debemos una vez más inyectar DbProvider en el constructor, también vamos a crear una variable miembro llamada sitios  de tipo any que contendrá un array con todos los sitios que tenemos guardados:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { DbProvider } from '../../providers/db/db';


/**
 * Generated class for the Listado page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */

@IonicPage()
@Component({
  selector: 'page-listado',
  templateUrl: 'listado.html',
})
export class Listado {
  sitios: any;

  constructor(public navCtrl: NavController, public navParams: NavParams,public db : DbProvider) {
  }

...

Ahora necesitamos extraer nuestros sitios de la base de datos cuando accedamos a la página del listado, podríamos hacerlo en el evento ionViewDidLoad que se ejecuta al cargar la página, el problema es que al contrario del modal, que se carga cada vez que lo llamamos, las paginas que se muestran al cambiar de pestaña se cargan una única vez al inicio, por lo que si guardamos nuevos sitios estos no se refrescaran a no ser que cierres la aplicación y la vuelvas a abrir.

Este es por lo tanto un buen momento para incorporar un nuevo concepto:

Ciclo de vida de una página

Cuando generamos una página con ionic generator vemos que nos crea por defecto el método ionViewDidLoad().

Como ya hemos comentado este método se ejecuta cuando la página se ha cargado:

ionViewDidLoad() {
    console.log('ionViewDidLoad ListadoPage');
 }

Vamos a ver los métodos con los que cuenta ionic en función de los eventos del ciclo de vida de una página:

  • ionViewDidLoad:
    Se ejecuta cuando la página se ha terminado de cargar.
       Este evento sólo ocurre una vez por página cuando se está creando.
       
  • ionViewWillEnter
       Se ejecuta cuando la página está a punto de entrar y convertirse en la página activa.

  • ionViewDidEnter:
       Se ejecuta cuando la página ha sido cargada y ahora es la página activa.
      
  • ionViewWillLeave:
       Se ejecuta cuando se está a punto de salir de la página y ya no será la página activa.

  • ionViewDidLeave:
       Se ejecuta cuando se ha salido de forma completa de la página
       y ya no es la página activa.

  • ionViewWillUnload:
       Se ejecuta cuando la página está a punto de ser destruida,
       ella y todos sus elementos.

Bien, una vez sabido esto, vamos a cargar nuestros sitios en el evento ionViewDidEnter, es decir cada vez que entremos en la página, una vez se ha cargado y está activa.

Añadimos el método ionViewDidEnter al controlador de la página listado en el archivo listado.ts y hacemos la llamada para extraer nuestros sitios de la base de datos:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { DbProvider } from '../../providers/db/db';


/**
 * Generated class for the Listado page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */
@IonicPage()
@Component({
  selector: 'page-listado',
  templateUrl: 'listado.html',
})
export class ListadoPage {
  sitios: any;

  constructor(public navCtrl: NavController, public navParams: NavParams,public db : DbProvider) {
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad Listado');
  }

  ionViewDidEnter(){
     this.db.getSitios().then((res)=>{
    this.sitios = [];
    for(var i = 0; i < res.rows.length; i++){
        this.sitios.push({
          id: res.rows.item(i).id,
          lat: res.rows.item(i).lat,
          lng: res.rows.item(i).lng,
          address: res.rows.item(i).address,
          description: res.rows.item(i).description,
          foto: res.rows.item(i).foto
        });
    }

    },(err)=>{ /* alert('error al sacar de la bd'+err) */ })
   }

}

Al llamar a this.db.getSitios obtenemos una promesa donde en res obtenemos el recurso devuelto por la base de datos. No podemos acceder directamente a los registros extraidos de la base de datos, debemos llamar a res.rows.item, y pasarle entre paréntesis el indice del elemento que queremos obtener, para ello recorremos res.rows mediante un bucle for y añadimos cada item al array this.sitios.

Al finalizar el bucle  this.sitios contendrá un array con todos los sitios que hemos guardados en la base de datos.

Ahora solo nos queda mostrarlos en el listado:

Vamos a editar la vista de la página listado, para ello abrimos el archivo listado.html, borramos el texto provisional que teníamos y lo dejamos de la siguiente manera:

<!--
  Generated template for the Listado page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>Listado</ion-title>
  </ion-navbar>

</ion-header>

<ion-content padding>
<ion-list>
  <ion-item *ngFor="let sitio of sitios">
    <ion-thumbnail item-left>
      <img [src]="sitio.foto">
    </ion-thumbnail>
    <h2>{{ sitio.address }}</h2>
    <p>{{ sitio.description }}</p>
  </ion-item>
</ion-list>
</ion-content>

Como vemos dentro de ion-content hemos creado un elemento ion-list, después con *ngFor recorremos nuestro array de sitios creando un elemento ion-item por cada iteración.

Con ion-thumbnail mostramos una miniatura de la foto del sitio, despues mostramos la dirección y la descripción.

Como veis no tiene mayor dificultad. Si queréis saber más sobre el componente ion-list y sus posibilidades podçeis consultar la documentación oficial siguiendo este enlace:

https://ionicframework.com/docs/components/#lists

Si todo ha ido bien la pantalla listado debería tener un aspecto similar a este, bueno… tal vez en tus fotos no salga un gato ;-P

 

Recordad que como estamos utilizando plugins nativos necesitamos ejecutar la aplicación en un emulador o en un dispositivo físico escribiendo en consola:

ionic cordova run android

Esto es todo de momento, hoy hemos aprendido como guardar datos en una base de datos local con SQLite, hemos aprendido a crear un provider para utilizarlo como servicio para gestionar los accesos a la base de datos y hemos aprendido como funcionan los ciclos de vida de un página.

En el siguiente post seguiremos avanzando con nuestra app. Hay muchas cosas que se pueden mejorar, aquí solo pretendo explicar los conceptos básicos para que podáis crear vuestras propias aplicaciones. Os animo a que experimentéis y tratéis de mejorar la app, es sin duda la mejor forma de aprender. Podéis compartir vuestra experiencias en los comentarios y así ayudar a los que tengan algún problema.

 

Si necesitas desarrollar una aplicación móvil no dudes en solicitarme un presupuesto sin compromiso:

Tutorial de Ionic – Crear una aplicación para guardar nuestros sitios geolocalizados – Parte 4 – Mostrando la dirección a partir de las coordenadas y sacando foto con la cámara.

En el siguiente enlace tienes el índice para acceder al resto de entradas de este tutorial:

 

¡¡Atención!! este tutorial se basa en ionic 3 y está desactualizado por lo que es posible que los ejemplos no funcionen en la última versión de ionic, haz click aquí para acceder a un tutorial mas actual de Ionic.

Hola a todos:

En el post anterior vimos como poner un marcador al mapa, aprendimos a utilizar los botones FAB y aprendimos a utilizar ventanas modales.

Hoy vamos a continuar desarrollando nuestra app, vamos a crear un pequeño formulario en el modal donde mediante una llamada a la api de google maps obtendremos y mostraremos la dirección a partir de las coordenadas y permitiremos introducir una descripción y  también tomar una fotografía del lugar.

Bien, vayamos por partes:

Ahora vamos a añadir al controlador del modal tres variables nuevas que vamos a necesitar:

  • Una variable de tipo string  que vamos a llamar address donde guardaremos la dirección que luego mostraremos en la vista.
  • Una variable de tipo string a la que vamos a llamar description y que contendrá la descripción del lugar que introduzcamos desde el formulario.
  • Por último una variable de tipo any que vamos a llamar foto, donde guardaremos una foto del lugar codificada en base 64.

Por lo tanto editamos el controlador de nuestro modal (modal-nuevo-sitio.ts) encima del constructor de la clase después de la variable coords definimos las siguientes variables:

...

export class ModalNuevoSitioPage {

  coords : any = { lat: 0, lng: 0 }
  address: string;
  description: string = '';
  foto: any = '';

  constructor(public navCtrl: NavController, public navParams: NavParams, private viewCtrl : ViewController) {}

...

Para obtener la dirección correspondiente a unas coordenadas Google maps cuenta con el elemento Geodecoder. 

Vamos a ver brevemente como funciona Google Maps  Geodecoder:

Para realizar una petición debemos crear un objeto dela clase Geodecoder y llamar al método geodecode pasandole las coordenadas y una función callback donde recibimos los datos en caso de tener éxito y el estado de la petición:

var geocoder = new google.maps.Geocoder();

geocoder.geocode({'location': coords} , function (results, status) {
    if (status == google.maps.GeocoderStatus.OK) {
       // en results tenemos lo que nos devuelve la llamada;
    } else {
      // Ha habido algún error
    }
});

Para obtener los resultados tenemos pasarle una función callback, sin embargo lo que nos interesa en que nos devuelva una promesa que es la mejor manera de gestionar las peticiones asíncronas desde el controlador.

Creando nuestras propias promesas

Ya vimos en el capitulo anterior como se tratan las funciones que nos devuelve una promesa utilizando then, pero si queremos que una función que creemos nosotros devuelva una promesa tenemos que devolver un objeto Promise donde la promesa se crea a partir de una función callback en el que ejecutaremos la sentencia que nos devolverá el resultado asíncrono y  llamaremos a las funciones pasadas como argumento resolve y reject. Si la operación se a ejecutado correctamente se llama a resolve, y si a ocurrido un error llamamos a reject.

Sabiendo esto para conseguir que geodecode nos devuelva la dirección en una promesa tenemos que utilizar un pequeño truco que consiste en crear una función que vamos a llamar getAddress y que haremos que nos devuelva una promesa con el resultado de la llamada a geodecode, para ello editamos el archivo modal-nuevo-sitio.ts añadimos la siguiente función en el controlador:

getAddress(coords):any {
    var geocoder = new google.maps.Geocoder();

    return new Promise(function(resolve, reject) {
        geocoder.geocode({'location': coords} , function (results, status) { // llamado asincronamente
            if (status == google.maps.GeocoderStatus.OK) {
                resolve(results);
            } else {
                reject(status);
            }
        });
    });
}

Como podemos ver la función retorna una promesa donde se le pasa como parámetro una función con resolve y reject, luego dentro de la función se ejecuta la llamada a geodecoder.geodecode pasándole las coordenadas y la función callback donde si se ha recibido como status google.maps.GeocoderStatus.OK significa que hemos recibido correctamente los datos y entonces ejecutamos resolve, de lo contrario ejecutamos reject.

De esta manera podemos hacer que geodecode nos devuelva una promesa.

Nota: al igual que hicimos en la pagina de inicio, para que Typescript no de error por no reconocer la clase google cuando la llamemos desde la función que vamos a crear, vamos a declarar la variable google justo debajo de los imports con declare var google: any; 

Ahora en el método ionViewDidLoad que se ejecuta cuando la página se ha cargado vamos ha hacer una llamada a getAddress pasándole las coordenadas que hemos recibido para obtener la dirección y asignársela a this.address:

ionViewDidLoad() {
    console.log('ionViewDidLoad ModalNuevoSitioPage');
    this.coords.lat = this.navParams.get('lat');
    this.coords.lng = this.navParams.get('lng');
   this.getAddress(this.coords).then(results=> {
        this.address = results[0]['formatted_address'];
      }, errStatus => {
          // Aquí iría el código para manejar el error
      });
  }

Un ejemplo de la estructura de datos que recibimos en results sería:

{
 "results": [ {
 "types": street_address,
 "formatted_address": "Etorbidea Abandoibarra, 2, 48001 Bilbo, Bizkaia, España",
 "address_components": [ {
 "long_name": "2",
 "short_name": "2",
 "types": street_number
 }, {
 "long_name": "Etorbidea Abandoibarra",
 "short_name": "Etorbidea Abandoibarra",
 "types": route
 }, {
 "long_name": "Bilbo",
 "short_name": "Bilbo",
 "types": [ "locality", "political" ]
 }, {
 "long_name": "Bizkaia",
 "short_name": "BI",
 "types": [ "administrative_area_level_2", "political" ]
 }, {
 "long_name": "Euskadi",
 "short_name": "PV",
 "types": [ "administrative_area_level_1", "political" ]
 }, {
 "long_name": "España",
 "short_name": "ES",
 "types": [ "country", "political" ]
 }, {
 "long_name": "48001",
 "short_name": "48001",
 "types": postal_code
 } ],
 "geometry": {
 "location": {
 "lat": 43.26861,
 "lng": -2.934380000000033
 },
 "location_type": "ROOFTOP",
 "viewport": {
 "southwest": {
 "lat": 43.26726101970851, 
 "lng": -2.9357289802915147
 },
 "northeast": {
 "lat": 43.26995898029151,
 "lng": -2.933031019708551
 }
 }
 }
 } ]
}

Lo que nos interesa obtener que es la dirección completa se encuentra en results[0][‘formatted_address’], por lo tanto  le asignamos este dato a this.address.

Mostrando las coordenadas y la  dirección.

Ahora vamos a añadir en la vista un componente ion-card  donde mostraremos las coordenadas y la dirección donde nos encontramos, editamos el archivo modal-nuevo-sitio.html para que quede de la siguiente manera:

<!--
  Generated template for the ModalNuevoSitio page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>Nuevo sitio</ion-title>
    <ion-buttons start>
      <button ion-button (click)="cerrarModal()">
        <ion-icon name="md-close"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>

</ion-header>

<ion-content padding>
  <ion-card>
    <ion-card-header>
      Localización actual
    </ion-card-header>
    <ion-card-content>
      <p><strong>lat:</strong>{{ coords.lat}}<br/>
      <strong>lng:</strong>{{coords.lng }}</p>
      <hr>
      <p>{{ address }}</p>
    </ion-card-content>
  </ion-card>
</ion-content>

Como podéis observar hemos creado un componente ion-card que consta a su vez de un elemento ion-card-header donde ponemos como título “Localización  actual”, y en ion-car-content pondremos el contenido que queremos mostrar, en este caso mostramos un elemento <p> donde mostramos el valor de las variables coords.lat y coords.lng que hemos definido en el controlador y que contendrán las coordenadas actuales.

Por otro lado mostramos otro elemento <p> con el contenido de la variable address que de momento no contiene nada pero que contendrá la dirección que corresponda con las coordenadas que hemos recogido.

Al pulsar en el FAB se abrirá el modal mostrando algo similar a esto:

Modal con las coordenadas y a dirección
Modal con las coordenadas y a dirección

Creando el formulario.

Vamos a seguir añadiendo elementos a la vista del modal. Además de las coordenadas y la dirección queremos dar la posibilidad de tomar una foto del lugar y escribir anotaciones.
Para ello vamos a crear dentro del card, debajo de la dirección un pequeño formulario donde habrá un botón para sacar una foto y un campo text-area para  escribir una descripción del lugar.

Editamos de nuevo modal-nuevo-sitio.html y añadimos lo que está marcado de amarillo:

<!--
  Generated template for the ModalNuevoSitio page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>Nuevo sitio</ion-title>
    <ion-buttons start>
      <button ion-button (click)="cerrarModal()">
        <ion-icon name="md-close"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>

</ion-header>

<ion-content padding>
  <ion-card>
    <ion-card-header>
     <strong>Localización actual</strong>
    </ion-card-header>
    <ion-card-content>
      <p><strong>lat:</strong>{{ coords.lat}}<br/>
      <strong>lng:</strong>{{coords.lng }}</p>
      <hr>
      <p>{{ address }}</p>
      <hr/>
      <form (ngSubmit)="guardarSitio()">
      <ion-item>
        <img [src]="foto" *ngIf="foto" />
        <button ion-button icon-left full type="button" (tap)="sacarFoto()">
          Foto&nbsp;&nbsp;
          <ion-icon name="camera"></ion-icon>
        </button>
      </ion-item>
      <hr/>
      <ion-item>
        <ion-label>Descripción</ion-label>
        <ion-textarea [(ngModel)]="description" name="description"></ion-textarea>
      </ion-item>
      <button ion-button type="submit" block>Guardar Sitio</button>
    </form>
    </ion-card-content>
    </ion-card>
</ion-content>

Como vemos al formulario le hemos añadido (ngSubmit), con esto le indicamos que cuando se ejecute el evento submit del formulario se ejecute la función guardarSitio definida en el controlador.

Después hemos añadido un elemento ion-item, donde vamos a mostrar la imagen con la fotografía que tomemos:

 <img [src]="foto" *ngIf="foto" />

Por un lado con [src] le indicamos que la imagen va a mostrar lo que contenga la variable foto, que hemos definido el el controlador y que al principio estará vacía, y por otro lado con *ngIf le indicamos que solo se muestre la imagen si dicha variable foto tiene un valor, es decir que solo se mostrará una vez hayamos tomado la fotografía.

Después tenemos un botón que al pulsar llama a la función sacarFoto que después definiremos en el controlador:

<button ion-button icon-left full type="button" (tap)="sacarFoto()">
          Foto&nbsp;&nbsp;
          <ion-icon name="camera"></ion-icon>
 </button>

Con icon-left le indicamos al botón que el icono se situará a la izquierda, para mostrar el icono después del texto “Foto” y un par de espacios en blanco “&nbsp;” insertamos el elemento ion-icon con  el icono camera.

Después tenemos un campo de tipo ion-textarea donde podremos introducir la descripción del sitio:

<ion-item>
    <ion-label>Descripción</ion-label>
    <ion-textarea [(ngModel)]="description" name="description"></ion-textarea>
</ion-item>

Por último tenemos un botón de tipo submit para enviar el formulario. Recordad que cuando pulsemos en este botón se disparará el evento ngSubmit y por lo tanto se ejecutará la función guardarSitio tal y como hemos definido en la etiqueta form.

Utilizando la cámara del teléfono móvil

Ahora vamos a ver como sacar una foto y para que se muestre en la etiqueta img que hemos creado:

Para poder utilizar la cámara del móvil tenemos que instalar el plugin cordova-plugin-camera con el siguiente comando:

ionic cordova plugin add cordova-plugin-camera --save
npm install --save @ionic-native/camera

Ahora el controlador del modal (modal-nuevo-sitio.ts) tenemos  que importar Camera desde ionic-native e inyectarlo en el constructor:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';
import { Camera, CameraOptions } from '@ionic-native/camera';

declare var google:any;
/**
 * Generated class for the ModalNuevoSitio page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */

@IonicPage()
@Component({
  selector: 'page-modal-nuevo-sitio',
  templateUrl: 'modal-nuevo-sitio.html',
})
export class ModalNuevoSitioPage {

  coords : any = { lat: 0, lng: 0 }
  address: string;
  description: string = '';
  foto: any = '';

  constructor(public navCtrl: NavController, public navParams: NavParams, private viewCtrl : ViewController, private camera: Camera) {
  }
...

También debemos importar Camera en app.module.ts y declararlo como provider:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { Geolocation } from '@ionic-native/geolocation';
import { Camera } from '@ionic-native/camera';

import { MyApp } from './app.component';


@NgModule({
  declarations: [
    MyApp,
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},Geolocation,Camera
  ]
})
export class AppModule {}

Ahora vamos a crear el método sacarFoto en en el controlador (modal-nuevo-sitio.ts) que se ejecutará al pulsar sobre el botón Foto.

En el archivo modal-nuevo-sitio.ts añadimos el siguiente código dentro del controlador:

sacarFoto(){

    let cameraOptions : CameraOptions = {
        quality: 50,
        encodingType: this.camera.EncodingType.JPEG,
        targetWidth: 800,
        targetHeight: 600,
        destinationType: this.camera.DestinationType.DATA_URL,
        sourceType: this.camera.PictureSourceType.CAMERA,
        correctOrientation: true
    }


    this.camera.getPicture(cameraOptions).then((imageData) => {
      // imageData is a base64 encoded string
        this.foto = "data:image/jpeg;base64," + imageData;
    }, (err) => {
        console.log(err);
    });
  }

Para sacar una foto utilizamos el método getPicture pasándole un array de opciones. En las opciones definimos las características que va a tener la imagen:

  • encodingType: Selecciona la codificación del archivo de imagen devuelto, puede ser JPEG o PNG.
  • targetWidth: Anchura de la foto.
  • targetHeight: Altura de la foto.
  • destinationType: Define el formato del valor devuelto, puede ser :
    • DATA_URL devuelve la imagen como una cadena codificada en base64.
    • FILE_URI: Crea un archivo con la imagen y devuelve la ruta al archivo.
    • NATIVE_URI: devuelve la ruta nativa al archivo (assets-library:// en iOS o content:// en Android).
  • sourceType: Indica el origen de la foto, puede ser:
    • CAMERA (por defecto).
    • PHOTOLIBRARY
    • SAVEDPHOTOALBUM
  • correctOrientation: Gira la imagen para corregir la orientación del dispositivo durante la captura.

Puedes visitar el siguiente enlace para conocer todas las opciones posibles y saber más sobre el plugin Camera:

https://ionicframework.com/docs/native/camera/

En this.foto guardamos la imagen codificada en formato base64 que recibimos de la cámara, para poder mostrarla como parte de la url en el parámetro src de la imagen tenemos que añadirle “data:image/jpeg;base64,” por delante.

Por si alguno se ha perdido muestro el contenido completo de como tiene que quedar en estos momentos el archivo modal-nuevo-sitio.ts:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams,  ViewController } from 'ionic-angular';
import { Camera, CameraOptions } from '@ionic-native/camera';

declare var google:any;

/*
  Generated class for the ModalNuevoSitio page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
*/
@IonicPage()
@Component({
  selector: 'page-modal-nuevo-sitio',
  templateUrl: 'modal-nuevo-sitio.html'
})
export class ModalNuevoSitioPage {

  coords : any = { lat: 0, lng: 0 }
  address: string;
  description: string = '';
  foto: any = '';

  constructor(public navCtrl: NavController, public navParams: NavParams, private viewCtrl : ViewController, private camera: Camera ) {}

  ionViewDidLoad() {
    console.log('ionViewDidLoad ModalNuevoSitioPage');
    this.coords.lat = this.navParams.get('lat');
    this.coords.lng = this.navParams.get('lng');
    this.getAddress(this.coords).then(results=> {
        this.address = results[0]['formatted_address'];
      }, errStatus => {
          // Aquí iría el código para manejar el error
      });
  }

  cerrarModal(){
    this.viewCtrl.dismiss();
  }

  getAddress(coords):any {
    var geocoder = new google.maps.Geocoder();

    return new Promise(function(resolve, reject) {
        geocoder.geocode({'location': coords} , function (results, status) { // llamado asincronamente
            if (status == google.maps.GeocoderStatus.OK) {
                resolve(results);
            } else {
                reject(status);
            }
        });
    });
  }

  sacarFoto(){

    let cameraOptions : CameraOptions = {
        quality: 50,
        encodingType: this.camera.EncodingType.JPEG,
        targetWidth: 800,
        targetHeight: 600,
        destinationType: this.camera.DestinationType.DATA_URL,
        sourceType: this.camera.PictureSourceType.CAMERA,
        correctOrientation: true
    }


    this.camera.getPicture(cameraOptions).then((imageData) => {
      // imageData is a base64 encoded string
        this.foto = "data:image/jpeg;base64," + imageData;
    }, (err) => {
        console.log(err);
    });
  }

}

Añadiendo plataformas

Bien, hasta ahora hemos estado probando nuestra aplicación en el navegador, sin embargo no podemos probar el plugin camera desde el navegador. Ha llegado la hora de probar nuestra aplicación en un dispositivo móvil.

Lo primero que tenemos que hacer es añadir la plataforma en la que queremos probar nuestra aplicación.

Para añadir una plataforma utilizamos el siguiente comando desde consola, recordad que debemos esta siempre dentro de la ios
ionic platform add wp

Por lo tanto si queremos probar nuestra app en android escribiremos:

ionic cordova platform add android

Esto nos creará una carpeta llamada platforms si no estaba creada, y añadirá una carpeta android con todo el código necesario para poder generar un archivo apk instalable.

En Mac y Linux tal vez os pida que escribáis sudo por delante.

Ejecutando nuestra app en el dispositivo móvil

Una vez tenemos añadida la plataforma si enchufamos nuestro móvil con un cable usb a nuestro pc podemos ejecutar la app directamente en el dispositivo con el siguiente comando:

ionic cordova run android

Si no disponéis de un dispositivo también podéis emular la aplicación utilizando ionic emulate:

ionic cordova emulate android

En ios también puedes entrar en la carpeta  platforms/ios y abrir el archivo con extensión .xcodeproj desde Xcode y ejecutarlo o emularlo desde Xcode.

El el móvil la app con el modal abierto se vería algo así:

Nuestra App funcionando en un dispositivo móvil.
Nuestra App funcionando en un dispositivo móvil.

Como podéis observar mi gato se ha prestado voluntariamente para posar en la foto ;-P

Dependiendo del sistema operativo que utilicéis en vuestro pc y del dispositivo puede que tengáis algún  problema para que os reconozca el móvil, yo no tengo tiempo de investigar cada caso pero googleando seguro que dais con la solución,  os animo a que dejéis en los comentarios si tenéis algún problema y que quién encuentre la solución lo ponga para ayudarnos unos a otros.

Por hoy lo dejamos aquí, en el siguiente capitulo veremos como guardar nuestros sitios en una base de datos local en nuestro dispositivo.

 

Si necesitas desarrollar una aplicación móvil no dudes en solicitarme un presupuesto sin compromiso:

Tutorial de Ionic – Crear una aplicación para guardar nuestros sitios geolocalizados – Parte 3: Añadiendo FAB, marcador y ventana modal.

En el siguiente enlace tienes el índice para acceder al resto de entradas de este tutorial:

 

¡¡Atención!! este tutorial se basa en ionic 3 y está desactualizado por lo que es posible que los ejemplos no funcionen en la última versión de ionic, haz click aquí para acceder a un tutorial mas actual de Ionic.

Hola a todos:

En el post anterior vimos como insertar en nuestra app un mapa de google maps centrado en las coordenadas actuales.
Vamos a continuar desarrollando nuestra app.

Para poder apreciar mejor donde estamos situados vamos a mostrar un marcador personalizado en el mapa que nos indique nuestra posición.

Para el marcador vamos a utilizar una imagen personalizada. La forma correcta de utilizar imágenes locales en nuestra app es alojarlas en la carpeta src/assets, así que vamos a crear dentro de src/assets una carpeta la la que llamaremos img donde alojaremos nuestras imágenes.

Podemos poner la imagen que queramos como  marcador para el mapa. Si no os queréis complicar podéis dar botón derecho sobre la siguiente imagen y descargarla para utilizarla en este ejemplo:

Bien, una vez descargada la imagen la copiamos en la carpeta img que acabamos de crear.

Ahora para situar el marcador en el mapa editamos el archivo inicio.ts y en la función loadMap() que muestra el mapa añadimos el siguiente código:

loadMap(){

   let mapContainer = document.getElementById('map');
    this.map = new google.maps.Map(mapContainer, {
      center: this.coords,
      zoom: 12
    });
    
    // Colocamos el marcador
    let miMarker = new google.maps.Marker({
              icon : 'assets/img/ico_estoy_aqui.png',
              map: this.map,
              position: this.coords
          });

}

No hay mucho que comentar, como veis para crear un marcador creamos un objeto google.maps.Marker  y le pasamos un objeto como parámetro donde en icon le indicamos donde se aloja la imagen del icono, en este caso la ruta a la imagen que hemos guardado destro de assets en la carpeta img es ‘assets/img/ico_estoy_aqui.png’, si vuestra imagen se llama de otra forma tenéis lógicamente que poner el nombre de la imagen que vayáis a utilizar.
En map le idicamos la variable que contiene el mapa donde se tiene que situar, es este caso this.map, por ultimo en position le asignamos las coordenadas donde se tiene que situar, en este caso le pasamos la variable this.coords que contiene las coordenadas actuales.

Ahora si ejecutamos ionic serve -l veremos en el navegador algo como esto:

Mostrando un marcador en el mapa
Mostrando un marcador en el mapa

Ahora vamos a dar un paso más y vamos a añadir un FAB (Floating Action Button) es decir botón de acción flotante al mapa.Los FAB son componentes estándar de material design, tienen la forma de un círculo y flotan sobre el contenido en una posición fija.

Este FAB lo utilizaremos para añadir la posición actual a nuestros sitios, para ello haremos que cuando se pulse en el fav se habra una ventana modal donde mostraremos un pequeño formulario donde aparecerán las coordenadas y la dirección de la posición actual y nos permitirá añadir una descripción y una fotografía desde la cámara de nuestro móvil.

Vayamos por partes.

Primero vamos a colocar el FAB en la vista de muestra página de inicio, editamos el archivo inicio.html y añadimos lo que esta marcado con fondo amarillo.

<!--
  Generated template for the Inicio page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>Inicio</ion-title>
  </ion-navbar>

</ion-header>

<ion-content padding>
  <div id="map"></div>
   <ion-fab right top>
    <button ion-fab (tap)="nuevoSitio()">
      <ion-icon name="pin"></ion-icon>
      <ion-icon name="add"></ion-icon>
    </button>
  </ion-fab>
  </ion-content>

Bien, como vemos tenemos el componente ion-fab al que le indicamos que se sitúe arriba a la derecha con right top.

En su interior contiene un botón al que le tenemos que indicar que es del tipo ion-fab.

Con (tap)=”nuevoSitio()” le indicamos que cuando se pulse o tape el botón se llame a la función nuevoSitio() que definiremos luego en el controlador de la página.

Después tenemos dos componentes ion-icon para mostrar los iconos en el FAB, uno con el icono pin y otro con el icono add, lo habitual es mostrar un solo icono, pero he querido poner dos para que quede más claro que queremos añadir una localización.

Si probamos ahora nuestra app tendrá un aspecto similar a este:

Bien, ahora vamos a definir la función nuevoSitio() en el controlador, editamos el archivo inicio.ts y añadimos la siguiente función debajo de la función loadMap:

nuevoSitio(){
 // aquí vamos a abrir el modal para añadir nuestro sitio.
}

La idea es que al llamar a esta función desde el FAB se abra un modal para poder añadir una descripción y una foto a muestro sitio si lo deseamos, vamos a explicar un poco que son los modales y como se utilizan:

Modales

Los modales son como ventanas que se abren dentro de nuestra aplicación sin que afecten a la pila de navegación.

Para crear un modal debemos de crear una página con el ionic generator.
Desde consola vamos a escribir el siguiente comando para crear el modal donde irá el formulario para introducir el nuevo sitio:

ionic g page modalNuevoSitio

Ahora en inicio.ts debemos importar el componente ModalController  de la librería ionic-angular, por lo tanto después de NavController y NavParams importamos también ModalController.

import { NavController, NavParams, ModalController } from 'ionic-angular';

Debemos inyectar también en el constructor el componente ModalController al que hemos llamado modalCtrl:

constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
    private geolocation: Geolocation,
    public modalCtrl : ModalController,
    platform: Platform) {

    ......

Ahora ya estamos listos para crear el modal en nuestra función nuevoSitio.

Para crear un modal utilizamos el método create del componente ModalController y le pasamos como primer parámetro un string con el nombre del  controlador de la página que hemos creado para el modal llamado ModalNuevoSitioPage.
El segundo parámetro es opcional y se utiliza para pasarle datos a nuestro modal, en este caso lo vamos a utilizar para pasarle el objeto this.coords que contiene las coordenadas que hemos obtenido.

Una vez creado el modal para que se muestre en pantalla invocamos al método present();

nuevoSitio(){
  // aquí vamos a abrir el modal para añadir nuestro sitio.
   let mimodal = this.modalCtrl.create( 'ModalNuevoSitioPage',this.coords );
   mimodal.present();
}

Ahora que sabemos como mostrar un modal y como pasarle datos desde la página que lo llama, vamos a ver como recibimos esos datos y los mostramos en el modal.

Recordad que al usar lazy loading y declarar la página del modal en su propio modulo (inicio.module.ts) no debemos declararlo en app.module.ts.

Vamos a crear una variable  en el controlador del modal (modal-nuevo-sitio.ts) que al igual que en la página inicio llamaremos coords y sera de tipo any, esta variable al igual que en la página de inicio va a contener un objeto con la latitud y longitud que recibimos en la llamada.

Para recibir los datos desde la página que llama al modal solo tenemos que utilizar el método get de NavParams que ya se importa por defecto al crear una página. Con navParams controlamos los parámetros que recibimos.
Dentro de la función ionViewDidLoad que se ejecuta al cargar la vista vamos a asignar a cada atributo del objeto coords  que acabamos de crear el valor que corresponde de la latitud y longitud que recibimos.
Veamos como tiene que quedar el código de modal-nuevo-sitio.ts en estos momentos:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';


/*
  Generated class for the ModalNuevoSitio page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
*/
@IonicPage()
@Component({
  selector: 'page-modal-nuevo-sitio',
  templateUrl: 'modal-nuevo-sitio.html'
})
export class ModalNuevoSitioPage {

  coords : any = { lat: 0, lng: 0 }

  constructor(public navCtrl: NavController, public navParams: NavParams) {}

  ionViewDidLoad() {
    console.log('ionViewDidLoad ModalNuevoSitioPage');
    this.coords.lat = this.navParams.get('lat');
    this.coords.lng = this.navParams.get('lng');
  }
}

Ahora que hemos recibido las coordenadas desde la página inicio vamos a mostrarlas en el modal para comprobar que las recibimos correctamente, para ello vamos a editar el archivo modal-nuevo-sitio.html y simplemente añadimos las coordenadas dentro de ion-content:

<ion-content padding>
  <p>Hemos recibido: {{ coords.lat }}, {{ coords.lng }}<p>
</ion-content>

Si probamos ahora muestra aplicación observamos que al pulsar el botón (FAB) que hemos creado para añadir sitios nos abre una ventana modal donde se muestran las coordenadas que acabamos de recibir, pero tenemos un pequeño problema, y es que no tenemos forma de cerrar el modal, así que antes de editar cualquier otra cosa en la página vamos a crear un botón de cerrar.

Para cerrar el modal tenemos que importar en el controlador de la pagina del modal (modal-nuevo-sitio.ts) el controlador ViewController e inyectarlo en el constructor.

Luego creamos una función que vamos a llamar cerrarModal donde utilizaremos  el método dismiss de ViewController para cerrarlo:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams,  ViewController } from 'ionic-angular';


/*
  Generated class for the ModalNuevoSitio page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
*/
@IonicPage()
@Component({
  selector: 'page-modal-nuevo-sitio',
  templateUrl: 'modal-nuevo-sitio.html'
})
export class ModalNuevoSitioPage {

  coords : any = { lat: 0, lng: 0 }
  constructor(public navCtrl: NavController, public navParams: NavParams, private viewCtrl : ViewController ) {}

  ionViewDidLoad() {
    console.log('ionViewDidLoad ModalNuevoSitioPage');
    this.coords.lat = this.navParams.get('lat');
    this.coords.lng = this.navParams.get('lng');
  }

  cerrarModal(){
    this.viewCtrl.dismiss();
  }

}

Ahora vamos a añadir el botón de cerrar que llamará a la función cerrarModal que acabamos de crear  en la cabecera de la vista de la página del modal (modal-nuevo-sitio.html), aprovechamos tambien para cambiar el título del modal a “Nuevo sitio”:

<!--
  Generated template for the ModalNuevoSitio page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>Nuevo sitio</ion-title>
   <ion-buttons start>
      <button ion-button (click)="cerrarModal()">
        <ion-icon name="md-close"></ion-icon>
      </button>
    </ion-buttons>
  </ion-navbar>

</ion-header>

<ion-content padding>
  <p>Hemos recibido: {{ coords.lat }}, {{ coords.lng }}<p>
</ion-content>

Ahora ya tenemos un botón de cerrar para nuestro modal, si habéis seguido correctamente todos los pasos al pulsar sobre el FAV se abrirá nuestro modal como en la siguiente imagen:

Por ahora lo dejamos aquí, hoy hemos aprendido como añadir un marcador al mapa de Google maps, como añadir a nuestra app botones tipo FAB, y como crear, abrir, pasar datos y cerrar un modal.
En el próximo post veremos como añadir un nuevo  con descripción y foto, haciendo uso de la cámara del móvil.

Si necesitas desarrollar una aplicación móvil no dudes en solicitarme un presupuesto sin compromiso:

Tutorial de Ionic – Crear una aplicación para guardar nuestros sitios geolocalizados – Parte 2: Mostrando el mapa.

En el siguiente enlace tienes el índice para acceder al resto de entradas de este tutorial:

Hola a todos:

En el post anterior creamos una aplicación con tres tabs y vimos como navegar entre ellas.
Hoy vamos a seguir construyendo la aplicación.Lo siguiente que vamos a hacer es mostrar un mapa en la página de inicio centrado en las coordenadas actuales donde nos encontremos.

Lo primero que vamos a necesitar es cagar la librería de Google maps , si quieres saber más sobre la api de google maps puedes consultar este tutorial.

Editamos el archivo index.html que es la plantilla principal que se carga al iniciar la aplicación y añadimos la siguiente linea antes de  cargar el script  build/main.js:

<script src="https://maps.google.com/maps/api/js"></script>

El código completo quedaría así:

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
  <meta charset="UTF-8">
  <title>Ionic App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">

  <link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
  <link rel="manifest" href="manifest.json">
  <meta name="theme-color" content="#4e8ef7">

  <!-- cordova.js required for cordova apps -->
  <script src="cordova.js"></script>

  <!-- un-comment this code to enable service worker
  
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('service-worker.js')
        .then(() => console.log('service worker installed'))
        .catch(err => console.log('Error', err));
    }
  -->

  <link href="build/main.css" rel="stylesheet">

</head>
<body>

  <!-- Ionic's root component and where the app will load -->
  <ion-app></ion-app>

  <!-- The polyfills js is generated during the build process -->
  <script src="build/polyfills.js"></script>

   <script  src="https://maps.google.com/maps/api/js"></script>

  <!-- The bundle js is generated during the build process -->
  <script src="build/main.js"></script>

</body>
</html>

Como vemos esta el la página principal donde se va a cargar todo el contenido de la app dentro de <ion-app></ion-app> que es componente raid de la aplicación.

Ahora para poder acceder a las coordenadas del móvil entra en juego ionic native.

Con ionic native lo que hacemos es instalar un plugin que nos da acceso a alguna característica nativa del móvil como la cámara, el gps, la brújula etc a través de typescript sin tener que programar nosotros en código nativo (Java para Android, Swift o objetive c para iOS).

Simplemente instalamos el plugin y luego importamos el plugin de ionic-native.

En la documentación de ionic  podemos ver todos los plugins disponibles en ionic native:

http://ionicframework.com/docs/v2/native/

En esta ocasión necesitamos el plugin cordova-plugin-geolocation.
Este plugin proporciona información sobre la ubicación del dispositivo, tales como la latitud y la longitud.

Para instalarlo desde el terminar dentro de la carpeta de nuestro proyecto tenemos que ejecutar los siguientes comandos:

ionic cordova plugin add cordova-plugin-geolocation --variable GEOLOCATION_USAGE_DESCRIPTION="La app necesita geolocalización"
npm install @ionic-native/geolocation --save

Nos sacará un mensaje diciendo que falta instalar el plugin @ionic/cli-plugin-cordova y nos preguntará si queremos instalarlo, le decimos que si.

Si estás utilizando Linux o Mac y te sale un error al intentar instalar el plugin prueba a ejecutar el comando con sudo por delante.

Si os muestra un warning como este:

npm WARN ajv-keywords@2.1.0 requires a peer of ajv@>=5.0.0 but none was installed.

Podeís solucionarlo escribiendo el siguiente comando:

npm install ajv@latest

Antes de nada debemos declarar el plugin como provider en el archivo app.module.ts, para ello editamos dicho archivo, importamos el plugin Geolocation y lo añadimos en la sección providers:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { Geolocation } from '@ionic-native/geolocation';


import { MyApp } from './app.component';


@NgModule({
  declarations: [
    MyApp,

  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    Geolocation
  ]
})
export class AppModule {}

Bien, ahora vamos a importar el plugin en el controlador de la página inicio, para ello dentro de la carpeta pages/inicio editamos el archivo inicio.ts e importamos el plugin Geolocation de ionic-native.
También vamos a importar Platform  desde ionic-angular para poder acceder al evento ready de Platform que se lanza cuando la aplicación se ha cargado completamente y esta lista. Esto nos evita errores al intentar llamar a un plugin antes de que se haya cargado.

Para importar estos dos módulos incluimos las siguientes lineas en los imports:

import { Geolocation } from '@ionic-native/geolocation';
import { Platform } from 'ionic-angular';

Ahora cuando la aplicación esté completamente cargada y lista (con el evento ready de Platform) vamos a obtener las coordenadas donde nos encontramos y mostrar un mapa centrado en las coordenadas actuales.

Creamos una variable miembro llamada map del tipo any (admite cualquier valor) que contendrá el manejador del mapa de google.
Después para poder utilizar Platform tenemos que inyectarlo como dependencia en el constructor de la clase, también debemos inyectar Geolocation en el constructor. En el evento ready de Platform llamaremos a una función que vamos a llamar obtenerPosicion. En estos momentos el código de inicio.ts debería quedarnos así:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';

import { Geolocation } from '@ionic-native/geolocation';
import { Platform } from 'ionic-angular';


/*
  Generated class for the Inicio page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
*/
@IonicPage()
@Component({
  selector: 'page-inicio',
  templateUrl: 'inicio.html'
})
export class InicioPage {

  map: any; // Manejador del mapa.

  constructor(
    public navCtrl: NavController,
    public navParams: NavParams,
   public  platform: Platform,
   private geolocation: Geolocation){

     platform.ready().then(() => {
      // La plataforma esta lista y ya tenemos acceso a los plugins.
        this.obtenerPosicion();
     });

  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad InicioPage');
  }

}

Hagamos un pequeño paréntesis en nuestra app para hablar de un par de conceptos que vamos a utilizar con frecuencia en nuestros desarrollos con ionic 2:

Promesas

Vemos que dentro del constructor lamamos al método ready()  de platform  y después de un punto utilizamos la palabra then(…).

Esto es lo que llamamos una promesa.

Normalmente los plugins de ionic-native nos devuelven una promesa

Las promesas las incluía angular 1 pero ahora las promesas ya son nativas en EMACScript 6.

Las promesas vienen a sustituir a los callbacks. Se ejecuta la  función y cuando obtenemos un resultado en el then se ejecuta el código que necesitemos.

Si falla la ejecución de alguno podemos utilizar la función catch para tratar el error.

Funciones de flecha

Otra cosa que te puede resultar rara es que dentro del then en lugar de ejecutar una función al uso estamos utilizando lo que se denomina funciones de flecha o en inglés (Fat Arrow functions).

La funciones con flecha permiten crear funciones anónimas más fácilmente y además permiten utilizar this en el contexto actual.
Vamos a ver un poco más en detalle esto con 
el siguiente ejemplo:

class MiClase {
  constructor() {
    this.nombre = 'Eduardo';
    setTimeout(() => {
      // Esto imprimé en la consola "Eduardo" ya que en las funciones con flecha this hace referencia al contexto actual.
      console.log(this.nombre);
    });
  }
}

Esto sería lo mismo que escribir lo siguiente:

class MiClase {
  constructor() {
    this.nombre = 'Eduardo';
    var _this = this;
    setTimeout(function() {
     console.log(_this.nombre);
    });
  }
}

Ahora que ya conocemos lo que son las promesas y las funciones de flecha podemos seguir con nuestra aplicación.

Antes de nada vamos a añadir una variable en en controlador  que vamos a vamos a llamar coords y en ella guardaremos un objeto con la latitud y longitud donde nos encontramos.

Por lo tanto encima del constructor de la clase después de la variable map definimos la variable coords:

...

export class Inicio {

  map: any; // Manejador del mapa.
 coords : any = { lat: 0, lng: 0 }

  constructor(

...

Si el editor os marca los errores observareis que os subraya this.obtenerPosicion(), esto es evidentemente porque estamos llamando a una función que todavía no hemos definido.

Vamos a definir la función obtenerPosición():

obtenerPosicion():any{
    this.geolocation.getCurrentPosition().then(res => {
      this.coords.lat = res.coords.latitude;
      this.coords.lng = res.coords.longitude;

      this.loadMap();
    })
    .catch(
      (error)=>{
        console.log(error);
      }
    );
  }

La función getCurrentPosition() del plugin Geolocation nos devuelve una promesa.

Como podemos observar resolvemos la promesa con una función de flecha. En la función de flecha recibimos como parámetro el objeto res. Lo que nos interesa obtener es la longitud y latitud donde nos encontramos, estos valores están en res.coords.longitude y res.coords.latitude.
Asignamos esos valores al objeto de la variable this.cords que acabamos de definir y después llamamos a la función this.loadMap() que aún no hemos creado.

Recordad que debemos de utilizar this para hacer referencia a las variables miembro y métodos que definamos en la clase del controlador.

Ahora deberemos crear la función loadMap que se encargará de mostrar un mapa en la página centrado en las coordenadas que hemos recogido.

Antes de definir la función necesitamos crear el contenedor donde se va a mostrar el mapa en la vista. La librería javascript de Google maps lo que hace es insertar un mapa en un div, por lo que necesitamos crear ese div en la vista y luego pasárselo como referencia para crear el mapa.

Editamos la vista de la página inicio, es decir el archivo inicio.html y añadimos un div dentro de ion-content  al que le asignamos como id “map”:

<!--
  Generated template for the Inicio page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>Inicio</ion-title>
  </ion-navbar>

</ion-header>


<ion-content padding>

  <div id="map"></div>

</ion-content>

Ahora tenemos que asignarle un tamaño al mapa así que por primera vez vamos a editar el archivo de estilos de una página, en este caso vamos a editar el archivo inicio.scss.

La extensión del archivo .scss, hace referencia a que es una archivo Sass, sus siglas hacen referencia a (Syntactically Awesome Stylesheets) algo así como “Hoja de estilo sintácticamente impresionante”.

Sass es un lenguaje de hoja de estilos que extiende el css tradicional proveyendo de varios mecanismos que están presentes en los lenguaje de programación. Con Sass puedes utilizar variables, código anidado, mixins, etc.

Ionic ya viene con Sass instalado lo que hace realmente fácil su utilización. Sass es un lenguaje de script que es traducido a css. Podemos añadir reglas de estilo de igual manera que lo hacemos con css por lo que de momento para definir el tamaño del mapa no necesitamos saber mas. Si quieres más información sobre Sass puede consultar la documentación oficial: http://sass-lang.com/documentation/

Veamos como tiene que quedar el archivo inicio.scss:

page-inicio {
ion-content{
    #map {
      width: 100%;
      height: 100%;
    }
  }
}

Como podemos ver dentro del elemento page-inicio definimos el estilo para ion-content  y a su vez dentro definimos el estilo para #map al que le estamos diciendo que ocupe todo al ancho y el alto de la página, como vemos Sass nos permite anidar los elementos.

Bien, una vez hechos estos preparativos ya podemos definir la función loadMap que se encargará de mostrar un mapa en la pagina centrado en las coordenadas que hemos recogido. Para que Typescript no de error por no reconocer la clase google cuando la llamemos desde la función que vamos a crear, vamos a declarar la variable google justo debajo de los imports con declare var google: any; . Veamos como tiene que quedar el código de inicio.ts con la variable google definida y nuestra función loadMap:

import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { Geolocation } from '@ionic-native/geolocation';
import { Platform } from 'ionic-angular';

declare var google: any;



/**
 * Generated class for the Inicio page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */
@IonicPage()
@Component({
  selector: 'page-inicio',
  templateUrl: 'inicio.html',
})
export class InicioPage {

 map: any; // Manejador del mapa.
 coords : any = { lat: 0, lng: 0 }

  constructor(public navCtrl: NavController, public navParams: NavParams,private geolocation: Geolocation,public  platform: Platform) {
    platform.ready().then(() => {
      // La plataforma esta lista y ya tenemos acceso a los plugins.
        this.obtenerPosicion();
     });

  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad Inicio');
  }

  loadMap(){
   let mapContainer = document.getElementById('map');
    this.map = new google.maps.Map(mapContainer, {
      center: this.coords,
      zoom: 12
    });
}

 obtenerPosicion():any{
    this.geolocation.getCurrentPosition().then(res => {
      this.coords.lat = res.coords.latitude;
      this.coords.lng = res.coords.longitude;

      this.loadMap();
    })
    .catch(
      (error)=>{
        console.log(error);
      }
    );
  }
}

En la función loadMap() asignamos a la variable mapContainer el elemento div con id=”map” que habíamos creado en la vista y creamos un mapa con google.maps.Map donde le pasamos dos parámetros, el primero es el elemento contenedor del mapa que lo hemos recogido en la variable mapContainer y el segundo parámetro es un objeto donde le pasamos la configuración del mapa, en este caso le pasamos las coordenadas al parámetro center y le decimos que muestre el mapa con un zoom de 12.

En este punto si probamos nuestra aplicación en el navegador con ionic serve -l deberíamos ver algo como esto:

Pantalla de Inicio con nuestro mapa
Pantalla de Inicio con nuestro mapa

Para no hacer demasiado largo este post lo vamos a dejar aquí.

En el siguiente post seguiremos desarrollando la app, veremos como añadir un botón FAB, como añadir un marcador al mapa y como crear una ventana modal y pasarle datos desde el controlador de la página que hace la llamada.

 
Si necesitas desarrollar una aplicación móvil no dudes en solicitarme un presupuesto sin compromiso: