Bloguera invitada Jen Looper
La poderosa combinación de NativeScript, Firebase y Angular 2 tiene la capacidad de potenciar la creación de apps. Esto puede ocurrir en especial durante los días festivos, cuando se enfrentan a la necesidad de acelerar el desarrollo de sus apps, ADEMÁS DE satisfacer las necesidades de sus familias respecto de la entrega de regalos. En el momento indicado, tengo la satisfacción de
presentarles (observen lo que hice 🎁) una demostración sobre la manera de aprovechar Firebase en sus apps de NativeScript con tecnología de Angular 2 usando varios elementos del famoso
complemento de Firebase para NativeScript, de Eddy Verbruggen.
En este instructivo, les mostraré la manera de usar cuatro elementos populares de Firebase en sus apps de NativeScript: la
autenticación, con una rutina de acceso y registro; la
base de datos, para el almacenamiento de datos y la actualización en tiempo real; la
configuración remota, para realizar cambios de manera remota en una app; y el
almacenamiento, para guardar fotos. Para hacer esto, decidí reescribir mi
app Giftler, originalmente escrita en Ionic.
A modo de preparación, los invito a leer la
documentación antes de comenzar con su proyecto para asegurarse de que se cumplan algunos requisitos previos:
- Asegúrense de que NativeScript esté instalado en sus equipos locales y de que CLI funcione como se espera.
- Configuren su IDE preferido para el desarrollo con NativeScript y Angular. Necesitarán TypeScript. Por lo tanto, asegúrense de que sus procesos de transpilación funcionen. Hay disponibles excelentes complementos de NativeScript para IDE compatibles con Visual Studio, Visual Studio Code y Jetbrains, entre otros. En particular, Visual Studio Code tiene fragmentos prácticos que agilizan el desarrollo.
- Accedan a sus cuentas de Firebase y encuentren sus consolas.
- Creen un nuevo proyecto en la consola de Firebase. Le puse el nombre “Giftler” al mío. Creen también una app de iOS y Android en la consola de Firebase. Como parte de este proceso, descargarán un archivo GoogleServices-Info.plist y un archivo google-services.json. Asegúrense de tomar nota de la ubicación que asignen a esos archivos, ya que los necesitarán pronto.
Instalen las dependencias
Creé Giftler como ejemplo de una app de NativeScript en la cual los usuarios pueden hacer una lista de los regalos que desearían recibir durante las fiestas, con la posibilidad de incluir fotos y descripciones de texto. Por el momento, esta app hace lo siguiente en iOS y Android:
- Permite el acceso, el cierre de sesión, el registro y una rutina de “olvido de contraseña”.
- Permite a los usuarios ingresar artículos de regalo en una lista.
- Permite a los usuarios borrar artículos de regalo de una lista.
- Permite a los usuarios editar artículos de la lista de manera individual agregando descripciones y fotos.
- Proporciona mensajería del servicio de configuración remota de Firebase que puede modificarse rápidamente en el backend.
Ahora, bifurquen el
código fuente de Giftler, que es una app completa y funcional. Una vez que sus apps estén clonadas, reemplacen los archivos orientados a Firebase actuales que descargaron al crearlas:
- En la
carpeta /app/App_Resources/Android
, dispongan el archivo google.services.json que descargaron de Firebase.
- De manera similar, en la
carpeta /app/App_Resources/iOS
dispongan el archivo GoogleService-Info.plist también descargado de Firebase.
Estos archivos se necesitan para inicializar Firebase en sus apps y conectarlas a los servicios externos correspondientes.
A continuación, veamos el archivo
package.json
y la raíz de esta app. Contiene los complementos que usarán en esta app. Presten atención a los complementos orientados a NativeScript:
"nativescript-angular": "1.2.0",
"nativescript-camera": "^0.0.8",
"nativescript-iqkeyboardmanager": "^1.0.1",
"nativescript-plugin-firebase": "^3.8.4",
"nativescript-theme-core": "^1.0.2",
El complemento “nativeScript-angular” representa la integración de Angular por parte de NativeScript. El complemento “camera” facilita un poco la administración de la cámara. “Iqkeyboardmanager” es un complemento específico de iOS que controla el teclado complejo en iOS. El complemento “theme” es una excelente opción para agregar estilos predeterminados a sus apps sin necesidad de cambiar su aspecto visual por completo. Por último, el complemento más importante de esta app es “Firebase”.
Una vez que las dependencias se encuentren en su lugar y los complementos estén listos para la instalación, podrán compilar sus apps para crear sus carpetas
platforms
con código específico de iOS y Android e inicializar el complemento de Firebase junto con el resto de los complementos basados en npm. Usando NativeScript CLI, diríjanse a la raíz de sus apps clonadas y escriban
tns run ios
o
tns run android.
Con esto, se iniciarán las rutinas de compilación de complementos y, en particular, verán el inicio de la instalación de las diferentes partes del complemento de Firebase. La secuencia de comandos que se ejecute les solicitará instalar varios elementos para integrarlos a los diferentes servicios de Firebase. Seleccionaremos todo, a excepción de la mensajería y la autenticación social por el momento. Una excelente característica consiste en que el archivo firebase.nativescript.json se instala en la raíz de la app. Por ello, si necesitan instalar una nueva parte del complemento posteriormente, pueden editar el archivo en cuestión y reinstalar el complemento.
En este punto, si ejecutan
tns livesync ios --watch
o
tns livesync android --watch
para ver la app en ejecución en un emulador y buscar cambios, verán que esta funciona y está lista para aceptar sus nuevas credenciales. Sin embargo, antes de inicializar un acceso asegúrense de que Firebase controle accesos de correo electrónico y contraseña habilitando esta función en la consola de Firebase, en la pestaña Authentication:
Veamos lo que sucede tras bambalinas. Para acceder a Firebase, deben inicializar los servicios de Firebase que instalaron. En
app/main.ts
, hay algunos elementos interesantes.
// this import should be first in order to load some required settings (like
globals and reflect-metadata)
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { AppModule } from "./app.module";
import { BackendService } from "./services/backend.service";
import firebase = require("nativescript-plugin-firebase");
firebase.init({
//persist should be set to false as otherwise numbers aren't returned during
livesync
persist: false,
storageBucket: 'gs://giftler-f48c4.appspot.com',
onAuthStateChanged: (data: any) => {
console.log(JSON.stringify(data))
if (data.loggedIn) {
BackendService.token = data.user.uid;
}
else {
BackendService.token = "";
}
}
}).then(
function (instance) {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
platformNativeScriptDynamic().bootstrapModule(AppModule);
Primero se importa firebase del complemento y luego se llama a .init(). Editen la propiedad storageBucket para que refleje el valor de la pestaña Storage de sus consolas de Firebase:
Ahora sus apps estarán personalizadas según sus cuentas de Firebase y podrán registrar un nuevo usuario y método de acceso en ellas. Pueden editar las variables user.email y password del archivo
app/login/login.component.ts
para cambiar las credenciales de acceso predeterminadas de
user@nativescript.org
a sus nombres de usuario y contraseñas si lo desean.
Pantallas de acceso de iOS y Android
Nota: usando el simulador de Xcode, tendrán la posibilidad de emular sus apps de inmediato en iOS. En Android, sin embargo, es posible que deban realizar algunos pasos para la instalación de la app en el emulador; por ejemplo, habilitar los servicios de Google. A continuación, se ofrece un
instructivo relacionado con la manera de hacerlo en Genymotion, mi emulador de Android favorito.
Estructura y autenticación de código
Los patrones de diseño de Angular 2 exigen dividir el código en módulos. Por ello, cumpliremos con este requisito usando la siguiente estructura de código:
—login
- login.component.ts
- login.html
- login.module.ts
- login.routes.ts
—list …
—list-detail …
—models
- gift.model.ts
- user.model.ts
- index.ts
—services
- backend.service.ts
- firebase.service.ts
- utils.service.ts
- index.ts
app.component.ts
app.css
app.module.ts
app.routes.ts
auth-guard.service.ts
main.ts
Presten atención a la forma en que la autenticación de Firebase funciona con auth-guard.service de Angular 2. Cuando Firebase se inicializa en sus apps en
app/main.ts
, como vimos antes, se llama a la función
onAuthStateChanged
:
onAuthStateChanged: (data: any) => {
console.log(JSON.stringify(data))
if (data.loggedIn) {
BackendService.token = data.user.uid;
}
else {
BackendService.token = "";
}
}
Cuando la app se inicie, busquen en la consola los datos convertidos en strings que muestre Firebase. Si este usuario se marca como
loggedIn
, simplemente configuraremos un
token
que represente el userId enviado por Firebase. Usaremos el módulo de configuración de NativeScript, que funciona como localStorage, para hacer que este userId siga estando disponible y asociarlo a los datos que creemos. Este token y las pruebas de autenticación que lo usan, administradas en el archivo
app/services/backend.service.ts
, quedan disponibles para el archivo
app/auth-guard.service.ts
. El archivo auth-guard ofrece una buena manera de administrar el estado de acceso y cierre de sesión de la app.
La clase AuthGuard implementa la interfaz CanActivate del módulo Router de Angular.
export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate() {
if (BackendService.isLoggedIn()) {
return true;
}
else {
this.router.navigate(["/login"]);
return false;
}
}
Básicamente, si el token se configura durante la rutina de acceso anterior, y la función BackendService.isLoggedIn muestra true, la app tiene autorización para navegar hasta la ruta predeterminada que representa nuestra lista de deseos. De lo contrario, el usuario regresa al acceso:
const listRoutes: Routes = [
{ path: "", component: ListComponent, canActivate: [AuthGuard] },
];
Ahora que inicializaron sus apps de NativeScript con tecnología de Firebase, veamos la forma de cargarles datos y de usar el increíble poder en tiempo real de Firebase para buscar la base de datos que se actualizará.
Creación de una lista y doble verificación de esta
A partir de
app/list/list.html
, que es la base de la lista de deseos, verán un campo de texto y una lista vacía. ¡Adelante, indíquenle a Santa lo que desean! Los artículos se envían a la base de datos y se agregan a sus listas en tiempo real. Veamos la manera de hacer esto.
Primero, observen que en
app/list/list.component.ts
se configura un elemento observable que contendrá la lista de regalos:
public gifts$: Observable;
Luego, se completa la lista desde la base de datos cuando se inicializa el componente:
ngOnInit(){
this.gifts$ = this.firebaseService.getMyWishList();
}
Lo interesante sucede en el archivo firebaseService. Observen la forma en que esta función agrega un receptor y muestra un elemento observable rxjs al buscar cambios en la colección Gifts de la base de datos de Firebase:
getMyWishList(): Observable {
return new Observable((observer: any) => {
let path = 'Gifts';
let onValueEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
console.log(JSON.stringify(results))
observer.next(results);
});
};
firebase.addValueEventListener(onValueEvent, `/${path}`);
}).share();
}
Los resultados de esta consulta se controlan en una función
handleSnapshot
como la siguiente, que filtra los datos por usuario y completa una matriz _allItems:
handleSnapshot(data: any) {
//empty array, then refill and filter
this._allItems = [];
if (data) {
for (let id in data) {
let result = (Object).assign({id: id}, data[id]);
if(BackendService.token === result.UID){
this._allItems.push(result);
}
}
this.publishUpdates();
}
return this._allItems;
}
Por último, se llama a publishUpdates, que ordena los datos por fecha para que se muestren primero los artículos más nuevos:
publishUpdates() {
// here, we sort must emit a *new* value (immutability!)
this._allItems.sort(function(a, b){
if(a.date < b.date) return -1;
if(a.date > b.date) return 1;
return 0;
})
this.items.next([...this._allItems]);
}
Una vez que el elemento observable $gifts se complete con datos, podrán editar y borrar elementos de él, y lo controlarán el receptor y el front end correctamente actualizados. Tengan en cuenta que la función onValueEvent del método getMyWishList incluye el uso de la clase
ngZone; esta garantiza que la IU se actualice como corresponde aunque los datos lo hagan de manera asincrónica.
Aquí se puede hallar buena información general sobre ngZone en apps de NativeScript.
Mensajes de origen lejano con configuración remota
En otra área atractiva del servicio de Firebase se incluye la “configuración remota”, una vía para proporcionar actualizaciones de apps desde el backend de Firebase. Pueden usar la configuración remota para activar o desactivar funciones en sus apps, realizar cambios en la IU o enviar mensajes de Santa, que es lo que vamos a hacer.
En
app/list/list.html
, encontrarán un cuadro de mensaje
<Label class="gold card" textWrap="true" [text]="message$ | async"></Label>
. La compilación del elemento observable
message$
es muy similar a la de la lista de datos; los cambios se recopilan en este caso cada vez que la app se inicializa de nuevo:
ngOnInit(){
this.message$ = this.firebaseService.getMyMessage();
}
Y la magia tiene lugar en la capa de servicio
(app/services/firebase.service.ts
):
getMyMessage(): Observable{
return new Observable((observer:any) => {
firebase.getRemoteConfig({
developerMode: false,
cacheExpirationSeconds: 300,
properties: [{
key: "message",
default: "Happy Holidays!"
}]
}).then(
function (result) {
console.log("Fetched at " + result.lastFetch + (result.throttled ? "
(throttled)" : ""));
for (let entry in result.properties)
{
observer.next(result.properties[entry]);
}
}
);
}).share();
}
Podrán publicar mensajes nuevos con la frecuencia que deseen.
Nota: la modificación reiterada de la configuración remota puede limitar sus instancias de Firebase. Por ello, deben realizar el desarrollo con cuidado.
Podrán tomar una foto.
Creo que uno de los aspectos más interesantes de este proyecto es la capacidad de tomar una foto del artículo deseado y guardarla en el almacenamiento de Firebase. Como lo mencioné antes, aproveché el complemento Camera; este facilita un poco la administración del hardware. En primer lugar, asegúrense de que sus apps tengan acceso a la cámara del dispositivo obteniendo permisos configurados en el método ngOnInit() de
app/list-detail/list-detail.component.ts
:
ngOnInit(){
camera.requestPermissions();
...
}
Una cadena de eventos se inicia cuando el usuario hace clic en el botón “Photo”, en la pantalla de detalles. Primero:
takePhoto() {
let options = {
width: 300"
height: 300"
keepAspectRatio: true,
saveToGallery: true
};
camera.takePicture(options)
.then(imageAsset => {
imageSource.fromAsset(imageAsset).then(res => {
this.image = res;
//save the source image to a file, then send that file path to
firebase
this.saveToFile(this.image);
})
}).catch(function (err) {
console.log("Error -> " + err.message);
});
}
La cámara toma una foto que luego se almacena como imageAsset y se muestra en pantalla. La imagen recibe un nombre y una fecha, y se guarda en un archivo local. Esa ruta de acceso se reserva para usos futuros.
saveToFile(res){
let imgsrc = res;
this.imagePath =
this.utilsService.documentsPath(`photo-${Date.now()}.png`);
imgsrc.saveToFile(this.imagePath, enums.ImageFormat.png);
}
Cuando se presiona el botón “Save”, esta imagen se envía a través de su ruta de acceso local a Firebase y se guarda en el módulo de almacenamiento. Su ruta de acceso completa en Firebase se devuelve a la app y se almacena en la colección de la base de datos de
/Gifts
:
editGift(id: string){
if(this.image){
//upload the file, then save all
this.firebaseService.uploadFile(this.imagePath).then((uploadedFile: any) =>
{
this.uploadedImageName = uploadedFile.name;
//get downloadURL and store it as a full path;
this.firebaseService.getDownloadUrl(this.uploadedImageName).then((downloadUrl:
string) => {
this.firebaseService.editGift(id,this.description,downloadUrl).then((result:any)
=> {
alert(result)
}, (error: any) => {
alert(error);
});
})
}, (error: any) => {
alert('File upload error: ' + error);
});
}
else {
//just edit the description
this.firebaseService.editDescription(id,this.description).then((result:any)
=> {
alert(result)
}, (error: any) => {
alert(error);
});
}
}
Esta cadena de eventos parece complicada, pero se resume a unas pocas líneas en el archivo de servicio de Firebase:
uploadFile(localPath: string, file?: any): Promise {
let filename = this.utils.getFilename(localPath);
let remotePath = `${filename}`;
return firebase.uploadFile({
remoteFullPath: remotePath,
localFullPath: localPath,
onProgress: function(status) {
console.log("Uploaded fraction: " + status.fractionCompleted);
console.log("Percentage complete: " + status.percentageCompleted);
}
});
}
getDownloadUrl(remoteFilePath: string): Promise {
return firebase.getDownloadUrl({
remoteFullPath: remoteFilePath})
.then(
function (url:string) {
return url;
},
function (errorMessage:any) {
console.log(errorMessage);
});
}
editGift(id:string, description: string, imagepath: string){
this.publishUpdates();
return firebase.update("/Gifts/"+id+"",{
description: description,
imagepath: imagepath})
.then(
function (result:any) {
return 'You have successfully edited this gift!';
},
function (errorMessage:any) {
console.log(errorMessage);
});
}
El resultado final es una buena alternativa para capturar fotos y descripciones de los regalos de sus listas de deseos. Santa ya no tendrá la excusa de que no sabía CUÁL lápiz de ojos Kylie comprar. Combinando el poder de NativeScript y Angular, pueden crear una app nativa de iOS y Android en minutos. Al agregar Firebase, pueden acceder a una poderosa alternativa para almacenar los usuarios, las imágenes y los datos de sus apps, y a un método para actualizar estos datos en tiempo real en diferentes dispositivos. Genial, ¿no es así? El aspecto resultante será el siguiente:

Hemos establecido las bases para crear una app de administración de listas de deseos sólida. Nos queda determinar la mejor manera de comunicar a Santa nuestros deseos: realizar una integración de correo electrónico de Mailgun o usar notificaciones de aplicación sería el próximo paso obvio. Mientras tanto, les hago llegar el deseo de que tengan unas felices fiestas y espero que disfruten mucho creando fabulosas apps de NativeScript con Firebase.
¿Desean más información sobre NativeScript? Visiten
http://www.nativescript.org. Si necesitan asistencia, súmense al canal de NativeScript en Slack
aquí.