Guardando datos
Algo habitual y necesario en las aplicaciones móviles es almacenar datos de forma persistente. De esta forma, podemos guardar datos para tenerlos disponibles en posteriores ejecuciones (por ejemplo, después de arrancar el dispositivo).Existe un módulo Nativescript que permite utilizar los "application settings" para guardar datos en la forma clave-valor. Este módulo es adecuado cuando los datos que se quieren guardar son muy sencillos. Pero otras veces tendremos que guardar datos más complejos, y en este caso necesitaremos una base de datos.
SQLite es una base de datos que funciona tanto en Android como en iOS de forma nativa. Nativescript tiene un plugin para poder utilizar SQLite directamente.
En este artículo haremos una pequeña aplicación para demostrar el uso de este plugin.
Creación del proyecto
En primer lugar creamos el proyecto:
tns create DatabaseApp
cd DatabaseApp
tns platform add android
tns plugin add nativescript-sqlite
Al añadir el plugin, se crea un nuevo directorio node_modules/nativescript-sqlite con los ficheros javascript que dan acceso a la base de datos Sqlite.
Código
Realizaremos una sencilla aplicación con una única pantalla: mainUtilizaremos javascript.
app/app.js
var application = require("application");
application.start({
moduleName: "main-page"
});
application.start({
moduleName: "main-page"
});
Como siempre, importamos el módulo application y lanzamos la página main-page para arrancar la aplicación.
app/main-page.xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo" class="page">
<StackLayout>
<TextField id="login" hint="write login" text="{{ login }}" />
<TextField id="mail" hint="write mail" text="{{ mail }}" />
<StackLayout orientation="horizontal">
<Button text="Insert" tap="{{ insert }}" />
<Button text="Select" tap="{{ select }}" />
</StackLayout>
</StackLayout>
</Page>
La página entrará en el callback onNavigatingTo cuando se cargue.
El primer StackLayout es vertical (por defecto) y contiene dos TextField (para introducir login y mail) y otro StackLayout horizontal con dos botones.
Los dos TextField rellenan el atributo texto con valores del modelo: login y mail.
Los dos Button llaman a funciones del modelo cuando se pulsan: insert y select.
app/main-page.js
var createViewModel = require("./main-view-model").createViewModel;
var Sqlite = require("nativescript-sqlite");
function onNavigatingTo(args) {
console.log("onNavigatingTo...");
// store page variable for bind model
var page = args.object;
// open database
var open = new Sqlite("my.db");
open.then(
db => {
console.log("OK opening database");
// create table if not exists
var create = db.execSQL("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, login TEXT, email TEXT)");
create.then(
id => {
console.log("OK creating database");
page.bindingContext = createViewModel(db);
},
error => {
console.log("Error opening database", error);
}
)
},
error => {
console.log("Error opening database", error);
}
);
}
exports.onNavigatingTo = onNavigatingTo;
Primero importamos lo que necesitamos: la función createViewModel que crearemos en el fichero main-view-model.js y el objeto Sqlite del plugin que hemos añadido al proyecto.
Después implementamos la funcion onNavigatingTo que será invocada cuando se cargue la página.
Además de sacar la variable page (que representa a la página) del argumento args, la primera cosa interesante de esta función es la apertura de la base de datos.
Antes de entrar en detalles, hay que decir que el plugin nativescript-sqlite trabaja con promesas javascript, así que es necesario estar familiarizado con las promesas para entender el código. En resumen, una promesa es similar a un callback, pero con una sintaxis más cómoda. Cuando una función ejecuta código asíncrono (o sea, que el resultado llegará posteriormente), puede retornar un objeto promesa. Este objeto tiene una función then() con dos parámetros. Estos dos parámetros son a su vez funciones, una para tratar el caso de ejecución correcta y otro para tratar el caso de error.
Quizás la sintaxis x => { } sea nueva para el lector, pero realmente es una simplificación de function(x) { }, es lo mismo.
En nuestro caso, new Sqlite("my.db") devuelve una promesa (que nosotros asignamos a la variable open). Después llamamos a open.then() pasando dos argumentos función. El primero es la función que se ejecutará cuando se abra correctamente la base de datos (esta función recibe como argumento un objeto db). Y el segundo es una función que se ejecutará cuando ocurra un error abriendo la base de datos (esta función recibe como argumento un objeto error, realmente un mensaje de error).
En el caso de que el fichero de base de datos se abra correctamente, la promesa invocará a la función que recibe el argumento db, y dentro de esa función podremos ejecutar sentencias contra la base de datos.
La primera cosa que hacemos con db es llamar a db.execSQL() para crear una tabla (si no existiera). Esta función, execSQL(), también devuelve una promesa, que guardamos en la variable create. Después llamamos a la función then() de la promesa pasando otras dos funciones: una para tratar el caso correcto y otra para tratar posibles errores.
En el caso de que la tabla se cree correctamente (o bien ya existía), entrará la función que recibe como argumento un id (el cual no vamos a utilizar). Lo que hacemos aquí es enlazar el modelo asociado a la página (page.bindingContext) a createViewModel(db), o sea, llamamos a la función createViewModel pasando la base de datos como argumento.
Documentación del plugin nativescript-sqlite.
app/main-view-model.js
var Observable = require("data/observable").Observable;
var Sqlite = require("nativescript-sqlite");
function createViewModel(database) {
var viewModel = new Observable();
viewModel.login = "";
viewModel.mail = "";
viewModel.insert = function() {
console.log("Inserting...");
var promise = database.execSQL("INSERT INTO users (login, email) VALUES (?, ?)", [ this.login, this.mail ]);
promise.then(
id => {
console.log("INSERT: ", id);
},
error => {
console.log("INSERT error", error);
}
);
}
viewModel.select = function() {
console.log("Selecting...");
var promise = database.all("SELECT * FROM users");
promise.then(
rows => {
for (var r in rows) {
console.log(r, "->", rows[r]);
}
},
error => {
console.log("SELECT error", error);
}
);
}
return viewModel;
}
exports.createViewModel = createViewModel;
En el fichero main-view-model.js definimos el modelo que vamos a utilizar en esta página.
En primer lugar, importamos Observable y Sqlite.
La función createViewModel() crea los objetos y funciones que constituyen el modelo (como hemos visto, esta función es llamada desde main-view.js para asociar el modelo a la página). Los atributos del objeto modelo son:
Las funciones insert() y select() llaman a la base de datos para insertar y consultar respectivamente.
Estas funciones se activan en el evento tap de los Button que vimos en el fichero main-view.xml
Las llamadas a base de datos funcionan con promesas, de forma similar a como vimos anteriormente.
var Sqlite = require("nativescript-sqlite");
function createViewModel(database) {
var viewModel = new Observable();
viewModel.login = "";
viewModel.mail = "";
viewModel.insert = function() {
console.log("Inserting...");
var promise = database.execSQL("INSERT INTO users (login, email) VALUES (?, ?)", [ this.login, this.mail ]);
promise.then(
id => {
console.log("INSERT: ", id);
},
error => {
console.log("INSERT error", error);
}
);
}
viewModel.select = function() {
console.log("Selecting...");
var promise = database.all("SELECT * FROM users");
promise.then(
rows => {
for (var r in rows) {
console.log(r, "->", rows[r]);
}
},
error => {
console.log("SELECT error", error);
}
);
}
return viewModel;
}
exports.createViewModel = createViewModel;
En el fichero main-view-model.js definimos el modelo que vamos a utilizar en esta página.
En primer lugar, importamos Observable y Sqlite.
La función createViewModel() crea los objetos y funciones que constituyen el modelo (como hemos visto, esta función es llamada desde main-view.js para asociar el modelo a la página). Los atributos del objeto modelo son:
- login: atributo que guarda el login
- mail: atributo que guarda el mail
- insert(): función para insertar un nuevo usuario
- select(): función para consultar la tabla de usuarios
Las funciones insert() y select() llaman a la base de datos para insertar y consultar respectivamente.
Estas funciones se activan en el evento tap de los Button que vimos en el fichero main-view.xml
Las llamadas a base de datos funcionan con promesas, de forma similar a como vimos anteriormente.