Building a multiple user app on Ionic 2 Adding Firebase Storage Unique Todo List Per User
In our previous two Firebase integration articles, I demonstrated how to add user authentication and read/write the data to and from the real-time Database in Firebase. But we stopped before adding a few additional feature that Firebase offers. I made this decision to cover different aspects in separate articles because Firebase is a big niche, with many features built tightly into a single package.
Firebase provides the Storage functionality that allows the developers (and users!) to upload their user-generated content to the server through the help of Google Cloud Storage. You can think of it as an alternative to Google Drive for uploading files to a web server in real-time. It has a lot of good features, such as, an ability to resume the download after a paused, security through authentication, and many more which we’ll continue to cover later on.
Through Firebase Storage we can upload the photos, videos and sound to the server. We can also take the pictures from the device’s camera and upload the snapshots to the Firebase Storage (which we will dive into very soon). And the best thing is that you can download the files on any device that is connected to the internet.
We have integrated the Todo List in our previous project, but if you have used any Todo List application, then you know that usually lists are created per user or unique lists are assigned to registered users of an application, as this assures that multiple users can use the same application, meaning the lists will have the information of the users to which they belong.
In this article, I am going to show you how to integrate the Firebase storage to our previous project and then I will add the unique Todo List Per User feature. Let’s get started!
Building a multiple user application on Ionic 2 – Part 3 – Adding Firebase Storage and Unique Todo List Per User
Ever wondered why people use Todo lists? In the modern world, our minds are often cluttered with things we need to do and perhaps various ideas , all running around in our heads all the time. But human beings seek out organization in everything, and have many realized that writing down your goals etc. in lists actually helps you to remember them and subsequently achieve them.
Even though there are many Todo list applications out there that allow you to write and save sophisticated Todo lists, it’s a great idea to create your own application. Perhaps you want to customize the application per your needs? After all, the more flexibility an application has, the better the user experience will be.
But, first let’s focus on integrating Firebase Storage. We will continue building upon our previous project, so if you already have the code saved, then no need to write the boilerplate code again.
Let’s create a new page for the file upload:
$ionic g page fileupload
We will be creating this page for integrating the camera plugin, which will allow the user to capture the photos through the device’s camera and then upload the pictures to Firebase Storage.
So, let’s add the required plugins:
$ ionic plugin add cordova-plugin-camera
$ ionic plugin add cordova-plugin-device
$ ionic plugin add cordova-plugin-file
Once these plugins are installed, open the “fileupload.ts” and copy the authentication code from “todo.ts”, as that is the same code we would use here for logging-in and authentication, something like the following:
fileupload.ts
…
…
// For Login
public userEmail: string;
public userPassword: string;
public authStatus: boolean;
public message: string;
private isAuth: BehaviorSubject<boolean>;
constructor(public navCtrl: NavController, public platform: Platform, private http: Http, private _cd:ChangeDetectorRef, private _user:UserService) {
this.isAuth = new BehaviorSubject(false);
this.isAuth.subscribe(val => this.authStatus = val);
}
ionViewDidLoad() {
console.log('ionViewDidLoad FileuploadPage');
this._user.auth.onAuthStateChanged(user => {
this.isAuth.next(!!user);
this._cd.detectChanges();
});
}
And the same for the HTML, something like:
fileupload.html
<div *ngIf='!authStatus'>
<h3>You need to login to be able to upload file to Firebase</h3>
<form (ngSubmit)="login()">
<ion-list>
<ion-item>
<ion-label stacked>Email</ion-label>
<ion-input [(ngModel)]="userEmail"
type="email" name="userEmail"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Password</ion-label>
<ion-input [(ngModel)]="userPassword"
type="password" name="userPassword"></ion-input>
</ion-item>
<hr/>
<button type="submit" ion-button>Login!</button>
</ion-list>
</form>
</div>
…
…
But, that’s just the authorization code. For the Firebase Storage integration, we would be storing the information of our “assets” in an array which we will iterate through in the HTML side. So, declare an array:
picturesArray: any;
Now, we need to add the method to load the data into this array:
loadData() {
firebase.database().ref('assets').on('value', (snapshot: any) => {
// We need to create this array first to store our local data
let rawList = [];
// Iterate to every value
snapshot.forEach((childSnapshot) => {
var element = childSnapshot.val();
element.id = childSnapshot.key;
rawList.push(element);
});
this.picturesArray = rawList;
});
}
The code should be easy to understand. We are creating a reference to our “assets” child, which holds all the data, and then we are storing the information in our array.
Once we have loaded the data, we will store the data in Blob format, as described here (https://firebase.google.com/docs/storage/web/upload-files). So, let’s create a method for it:
convertIntoBlob(imagePath) {
return new Promise((resolve, reject) => {
window.resolveLocalFileSystemURL(imagePath, (fileEntry) => {
fileEntry.file((resFile) => {
var reader = new FileReader();
reader.onloadend = (evt: any) => {
var imgBlob: any = new Blob([evt.target.result], { type: 'image/jpeg' });
imgBlob.name = 'sample.jpg';
resolve(imgBlob);
};
reader.onerror = (e) => {
console.log('Failed file read: ' + e.toString());
reject(e);
};
reader.readAsArrayBuffer(resFile);
});
});
});
}
Once the file is loaded to Blob, we need to upload it to Firebase. Of course, we need to first take the images from the device’s camera. Following is the complete content of “fileupload.ts”:
fileupload.ts
import { Component } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { Http } from '@angular/http';
import { Camera, Device } from 'ionic-native';
import { ChangeDetectorRef } from '@angular/core';
import { BehaviorSubject } from 'rxjs/Rx';
import { UserService } from '../../providers/user/user.service';
import * as firebase from 'firebase';
declare var window: any;
@Component({
selector: 'page-fileupload',
templateUrl: 'fileupload.html'
})
export class FileuploadPage {
picturesArray: any;
// For Login
public userEmail: string;
public userPassword: string;
public authStatus: boolean;
public message: string;
private isAuth: BehaviorSubject<boolean>;
constructor(public navCtrl: NavController, public platform: Platform, private http: Http, private _cd:ChangeDetectorRef, private _user:UserService) {
this.isAuth = new BehaviorSubject(false);
this.isAuth.subscribe(val => this.authStatus = val);
}
ionViewDidLoad() {
console.log('ionViewDidLoad FileuploadPage');
this._user.auth.onAuthStateChanged(user => {
this.isAuth.next(!!user);
this._cd.detectChanges();
});
}
ngOnInit() {
// Let's load our data here
this.loadData();
}
loadData() {
firebase.database().ref('assets').on('value', (snapshot: any) => {
// We need to create this array first to store our local data
let rawList = [];
// Iterate to every value
snapshot.forEach((childSnapshot) => {
var element = childSnapshot.val();
element.id = childSnapshot.key;
rawList.push(element);
});
this.picturesArray = rawList;
});
}
convertIntoBlob(imagePath) {
// https://firebase.google.com/docs/storage/web/upload-files
return new Promise((resolve, reject) => {
window.resolveLocalFileSystemURL(imagePath, (fileEntry) => {
fileEntry.file((resFile) => {
var reader = new FileReader();
reader.onloadend = (evt: any) => {
var imgBlob: any = new Blob([evt.target.result], { type: 'image/jpeg' });
imgBlob.name = 'image.jpg';
resolve(imgBlob);
};
reader.onerror = (e) => {
console.log('Failed file read: ' + e.toString());
reject(e);
};
reader.readAsArrayBuffer(resFile);
});
});
});
}
uploadToFirebase(imageBlob) {
// Let's use a simple name
var fileName = 'image-' + new Date().getTime() + '.jpg';
return new Promise((resolve, reject) => {
var fileRef = firebase.storage().ref('images/' + fileName);
var uploadTask = fileRef.put(imageBlob);
uploadTask.on('state_changed', (snapshot) => {
console.log('snapshot progess ' + snapshot);
}, (error) => {
reject(error);
}, () => {
resolve(uploadTask.snapshot);
});
});
}
saveToDatabaseAssetList(uploadSnapshot) {
var ref = firebase.database().ref('assets');
return new Promise((resolve, reject) => {
var dataToSave = {
'URL': uploadSnapshot.downloadURL,
'name': uploadSnapshot.metadata.name,
'owner': firebase.auth().currentUser.uid,
'email': firebase.auth().currentUser.email,
'lastUpdated': new Date().getTime(),
};
ref.push(dataToSave, (response) => {
resolve(response);
}).catch((error) => {
reject(error);
});
});
}
doGetPicture() {
let imageSource = (Device.isVirtual ? Camera.PictureSourceType.PHOTOLIBRARY : Camera.PictureSourceType.CAMERA);
Camera.getPicture({
destinationType: Camera.DestinationType.FILE_URI,
sourceType: imageSource,
targetHeight: 640,
correctOrientation: true
}).then((imagePath) => {
return this.convertIntoBlob(imagePath);
}).then((imageBlob) => {
return this.uploadToFirebase(imageBlob);
}).then((uploadSnapshot: any) => {
return this.saveToDatabaseAssetList(uploadSnapshot);
}).then((uploadSnapshot: any) => {
//alert('file saved to asset catalog successfully ');
}, (error) => {
alert('Error ' + (error.message || error));
});
}
public logout() {
this._user.logout()
}
public login() {
this._user.login(this.userEmail, this.userPassword)
}
}
Here, I have defined the methods for our Storage operation.
Now, open the “fileupload.html” and copy the following content:
fileupload.html
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>File Upload</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<div *ngIf='!authStatus'>
<h3>You need to login to be able to upload file to Firebase</h3>
<form (ngSubmit)="login()">
<ion-list>
<ion-item>
<ion-label stacked>Email</ion-label>
<ion-input [(ngModel)]="userEmail"
type="email" name="userEmail"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Password</ion-label>
<ion-input [(ngModel)]="userPassword"
type="password" name="userPassword"></ion-input>
</ion-item>
<hr/>
<button type="submit" ion-button>Login!</button>
</ion-list>
</form>
</div>
<div *ngIf='authStatus'>
<button ion-button (click)="doGetPicture()">Take Picture</button>
<ion-list>
<ion-item *ngFor="let item of picturesArray; let i = index; trackBy:index">
<p>{{i}} - {{item.name}}</p>
<p>{{item.lastUpdated}}</p>
<p>{{item.email}}</p>
<img [src]=item.URL width="180px" class="padding"/>
</ion-item>
</ion-list>
<button (click)='logout()' ion-button>Logout</button>
</div>
</ion-content>
Make sure this page is added in “app.component.ts” and “app.module.ts”. You can do “ionic serve” now to see the results, but you won’t be able to take screenshots from computer, so instead build it for Android or iOS.
Adding per user Todo List
In order to add the per use Todo List functionality, we need to add the email in child of our todo list in JSON object, as that will allow us to show the unique todo lists. We will create an array to store the information of our lists:
public todoArray: any;
Now, we need a method to store the values to the JSON database in Firebase and show the results in our page:
showData() {
var self = this;
var user = firebase.auth().currentUser;
if (user) {
// User is signed in.
this.userEmail = user.email;
} else {
// No user is signed in.
}
var ref = firebase.database().ref('/todo/');
ref.once('value').then(function(snapshot) {
// We need to create this array first to store our local data
let rawList = [];
snapshot.forEach( snap => {
if (snap.val().email == self.userEmail) {
rawList.push({
id: snap.key,
email: snap.val().email,
title: snap.val().title,
description: snap.val().description,
});
}
});
self.todoArray = rawList;
});
}
We will use this method whenever we need to refresh our page, meaning in our constructor, login and logout page, and when we are adding the items.
Here is the complete content of “todo.ts”:
todo.ts
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { ModalController } from 'ionic-angular';
import { DataService } from '../../providers/data/data.service';
import { TodoAddItem } from '../todo-add-item/todo-add-item'
import { TodoItemInfo } from '../todo-item-info/todo-item-info';
import { ChangeDetectorRef } from '@angular/core';
import { BehaviorSubject } from 'rxjs/Rx';
import { UserService } from '../../providers/user/user.service';
import firebase from 'firebase';
@Component({
selector: 'page-todo',
templateUrl: 'todo.html'
})
export class TodoPage {
// For Login
public userEmail: string;
public userPassword: string;
public authStatus: boolean;
public message: string;
private isAuth: BehaviorSubject<boolean>;
public todoArray: any;
ionViewDidLoad() {
console.log('ionViewDidLoad TodoPage');
this._user.auth.onAuthStateChanged(user => {
this.isAuth.next(!!user);
this._cd.detectChanges();
});
}
public items = [];
constructor(public navCtrl: NavController, private _data: DataService, public modalCtrl: ModalController, private _user:UserService, private _cd:ChangeDetectorRef){
this.isAuth = new BehaviorSubject(false);
this.isAuth.subscribe(val => this.authStatus = val);
this._data.getData().then((todos) => {
if(todos){
this.items = JSON.parse(todos);
}
});
this.showData();
}
showData() {
var self = this;
var user = firebase.auth().currentUser;
if (user) {
// User is signed in.
this.userEmail = user.email;
} else {
// No user is signed in.
}
var ref = firebase.database().ref('/todo/');
ref.once('value').then(function(snapshot) {
// We need to create this array first to store our local data
let rawList = [];
snapshot.forEach( snap => {
if (snap.val().email == self.userEmail) {
rawList.push({
id: snap.key,
email: snap.val().email,
title: snap.val().title,
description: snap.val().description,
});
}
});
self.todoArray = rawList;
});
}
addItem(){
let addModal = this.modalCtrl.create(TodoAddItem);
addModal.onDidDismiss((item) => {
if(item){
this.storeItem(item);
this.showData();
}
});
addModal.present();
}
storeItem(item){
this.items.push(item);
}
showItem(item){
// Let's pass the item param
this.navCtrl.push(TodoItemInfo, {
item: item,
"parentPage": this
});
}
public logout() {
this._user.logout()
this.showData();
}
public login() {
this._user.login(this.userEmail, this.userPassword)
this.showData();
}
}
If you look at rthe “showItem()” method, we are passing the “this” to our “TodoItemInfo” page, so that we can call the “showData()” to refresh our UI.
Now, open the “todo.html” and copy the following content:
todo.html
<ion-header>
<ion-navbar>
<button ion-button menuToggle>
<ion-icon name="menu"></ion-icon>
</button>
<ion-title>Todo</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<div *ngIf='!authStatus'>
<h3>You need to login to see the Todo list</h3>
<form (ngSubmit)="login()">
<ion-list>
<ion-item>
<ion-label stacked>Email</ion-label>
<ion-input [(ngModel)]="userEmail"
type="email" name="userEmail"></ion-input>
</ion-item>
<ion-item>
<ion-label stacked>Password</ion-label>
<ion-input [(ngModel)]="userPassword"
type="password" name="userPassword"></ion-input>
</ion-item>
<hr/>
<button type="submit" ion-button>Login!</button>
</ion-list>
</form>
</div>
<div *ngIf='authStatus'>
<p>Current User: {{userEmail}}</p>
<button ion-button (click)="addItem()">Add Item</button>
<!-- <ion-list>
<ion-item *ngFor="let item of items" (click)="showItem(item)">{{item.title}}</ion-item>
</ion-list> -->
<ion-list>
<ion-item *ngFor="let item of todoArray" (click)="showItem(item)">
<!-- <p>Id: <strong>{{item.id}}</strong></p> -->
<!-- <p>Email: <strong>{{item.email}}</strong></p> -->
<p>Title: <strong>{{item.title}}</strong></p>
<p>Description: <strong>{{item.description}}</strong></p>
</ion-item>
</ion-list>
<button (click)='logout()' ion-button>Logout</button>
</div>
</ion-content>
This code should be self-explanatory.
Now we need to add the code for updating the JSON table. Open the “todo-add-item.ts” and copy the following content:
todo-add-item.ts
import { Component } from '@angular/core';
import { NavController, ViewController } from 'ionic-angular';
import firebase from 'firebase';
@Component({
selector: 'todo-add-item',
templateUrl: 'todo-add-item.html'
})
export class TodoAddItem {
title;
description;
userEmail;
constructor(public nav: NavController, public view: ViewController) {
}
ionViewDidLoad() {
console.log('ionViewDidLoad TodoAddItem');
}
storeItem(){
let newItem = {
title: this.title,
description: this.description
};
this.view.dismiss(newItem);
var self = this;
var userId = firebase.auth().currentUser.uid;
console.log(userId);
var user = firebase.auth().currentUser;
this.userEmail = user.email;
// A post entry.
var postData = {
email: this.userEmail,
title: this.title,
description: this.description
};
// Get a key for a new Post.
var newPostKey = firebase.database().ref().child('todo').push().key;
// Write the new post's data simultaneously in the posts list and the user's post list.
var updates = {};
updates['/todo/' + newPostKey] = postData;
//updates['/todo/' + uid + '/' + newPostKey] = postData;
firebase.database().ref().update(updates);
}
close(){
this.view.dismiss();
}
}
Here, you can see that I have defined the method “storeItem()” that is adding the values to our JSON through the push() method. You can read more about it here (https://firebase.google.com/docs/database/web/read-and-write).
Now, open the “todo-add-item.html” and copy the following content:
todo-add-item.html
<ion-header>
<ion-toolbar color="secondary">
<ion-title>
Add Item
</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="close()"><ion-icon name="close"></ion-icon></button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>
<ion-label floating>Title</ion-label>
<ion-input type="text" [(ngModel)]="title"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>Description</ion-label>
<ion-input type="text" [(ngModel)]="description"></ion-input>
</ion-item>
</ion-list>
<button full ion-button color="secondary" (click)="storeItem()">Save</button>
</ion-content>
Now, we need to add the delete button as well in our list, so that the users can delete the lists on the fly. Open the “todo-item-info.ts” and copy the following content:
todo-item-info.ts
import { Component } from '@angular/core';
import { NavParams } from 'ionic-angular';
import firebase from 'firebase';
import { NavController } from 'ionic-angular';
@Component({
selector: 'page-todo-item-info',
templateUrl: 'todo-item-info.html'
})
export class TodoItemInfo {
email;
title;
description;
id;
constructor(public navCtrl: NavController, public navParams: NavParams) {
}
ionViewDidLoad() {
console.log('ionViewDidLoad TodoItemInfo');
this.email = this.navParams.get('item').email;
this.title = this.navParams.get('item').title;
this.description = this.navParams.get('item').description;
this.id = this.navParams.get('item').id;
}
delete() {
var userId = firebase.auth().currentUser.uid;
firebase.database().ref('todo/' + this.id).set({
email: null,
title: null,
description : null
});
this.navParams.get('parentPage').showData()
this.navCtrl.pop();
}
}
Now open the “todo-item-info.html” and copy the following content:
todo-item-info.html
<ion-header>
<ion-navbar color="secondary">
<ion-title>
{{email}}
</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-card>
<ion-card-content>
{{title}}
</ion-card-content>
</ion-card>
<ion-card>
<ion-card-content>
{{description}}
</ion-card-content>
</ion-card>
<button ion-button (click)="delete()">Delete it</button>
</ion-content>
You can do “ionic run android” if you have an Android device to test the application. You can use your creativity to add other new features, for example, the ability to edit the todo lists instead of deleting and creating new ones or you can add the functionality to show only the pictures that belong to the user who is currently logged in. The possibilities are endless.
By the way, there are packages to the Firebase service. If you are using the Free service currently, then know that it has limitations, for example, only 5 GB is supported for Storage, and many more limitations. You can read more here (https://firebase.google.com/pricing/).
…and that’s it! If you have trouble following the code, then I have uploaded the repository to Github here (https://github.com/danyalzia/Ionic-2-Firebase-Integration-Play).
Conclusion
There is no doubt that Firebase is a great service for backend support in Ionic 2 as it pretty much provides every feature a backend server needs for the modern applications. With enough knowledge of Firebase and Ionic 2, you can create every simple applications that perform the trivial tasks to giant e-commerce websites.
If you have any questions, feel free to leave a comment below!
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment