Archivo de la etiqueta: ionic 2 SQLite

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: