Uploading Pictures to Firebase Cloud Storage

First Published: 2 July 2018
Updated on: 24 July 2019

This is the sixth post of a 7 post series that will teach you how to build an app using Ionic Framework and Firebase from scratch.

In this chapter we are going to be using one of Firebase’s best features, Storage, it will let you store binary data in your Firebase application, meaning you can upload files :)

We are also going to use the phone’s camera (that’s why we installed the Capacitor in Chapter #1), the idea is to let our users take a picture of their guests when they are adding them to the guest list.

Since we already have everything installed let’s jump into the code.

Taking pictures

Now everything is ready to start working with the Camera API, so go to event-detail.page.html and create a button to take the guest’s picture, we’ll add a nice camera looking icon next to the guest name:

<ion-card class="add-guest-form">
  <ion-card-header> Add Guests </ion-card-header>
  <ion-card-content>
    <ion-row>
      <ion-col size="8">
        <ion-item>
          <ion-label position="stacked">Name</ion-label>
          <ion-input
            [(ngModel)]="guestName"
            type="text"
            placeholder="What's your guest's name?"
          ></ion-input>
        </ion-item>
      </ion-col>

      <ion-col size="4">
        <ion-button (click)="takePicture()">
          <ion-icon slot="icon-only" name="camera"></ion-icon>
        </ion-button>
      </ion-col>
    </ion-row>
    <span *ngIf="guestPicture">Picture taken!</span>

    <ion-button
      color="primary"
      expand="block"
      (click)="addGuest(guestName)"
      [disabled]="!guestName"
    >
      Add Guest
    </ion-button>
  </ion-card-content>
</ion-card>

Right after the name input I am adding a message that says the picture was taken and it only shows if the guestPicture property exists (that will make more sense in the event-detail.page.ts file)

And then a button to call the takePicture() function.

Now go to event-detail.ts and first import the Camera plugin:

import { Plugins, CameraResultType } from '@capacitor/core';
const { Camera } = Plugins;

After that you will create a variable to hold the guest’s picture, right before the constructor() add:

public guestPicture: string = null;

And add that property as a parameter in the addGuest function:

Before:

addGuest(guestName: string): void {
  this.eventService
    .addGuest(guestName, this.currentEvent.id, this.currentEvent.price)
    .then(() => (this.guestName = ''));
}

Now:

addGuest(guestName: string): void {
  this.eventService
    .addGuest(
      guestName,
      this.currentEvent.id,
      this.currentEvent.price,
      this.guestPicture
    )
    .then(() => {
      this.guestName = '';
      this.guestPicture = null;
    });
}

We are passing the this.guestPicture variable to the addGuest() function on our event provider, don’t worry if it gives you an error, the function is not declared for those parameters, and we will fix that once we move to edit our provider.

Then we are setting this.guestPicture to null to make sure the message “picture taken” is not shown.

Now we need to create the takePicture() function that’s going to open the camera and allow us to take a picture of our guest, and it is an extended function, so I am going to paste it here and then explain the different parts of it:

async takePicture(): Promise<void> {
  try {
    const profilePicture = await Camera.getPhoto({
      quality: 90,
      allowEditing: false,
      resultType: CameraResultType.Base64,
    });
    this.guestPicture = profilePicture.base64String;
  } catch (error) {
    console.error(error);
  }
}

There we are calling the Camera API from Capacitor and giving it a few options, most of them are obvious by their names, the most important one is resultType because it’s the one that will give you the format of the image, either a base64 string or the native path to the actual file.

We’re using the base64 string because Firebase Cloud Storage has a .putString() method that takes a base64 string and uploads the picture from it.

The next part of the code is setting that result to this.guestPicture.

This will now be sent in the addGuest() function from above, so it is time to move to our provider and edit that.

Go to the event.service.ts and find the addGuest() function:

addGuest(guestName: string, eventId: string, eventPrice: number): Promise<void> {
  return this.eventListRef
    .doc(eventId)
    .collection('guestList')
    .add({ guestName })
    .then((newGuest) => {
      return firebase.firestore().runTransaction(transaction => {
        return transaction.get(this.eventListRef.doc(eventId)).then(eventDoc => {
          const newRevenue = eventDoc.data().revenue + eventPrice;
          transaction.update(this.eventListRef.doc(eventId), { revenue: newRevenue });
        });
      });
    });
}

The first thing to do here is to add the picture parameter:

addGuest(
  guestName: string,
  eventId: string,
  eventPrice: number,
  guestPicture: string = null
): Promise<void> {...}

I am adding it and setting a default to null in case the guest does not want his picture taken.

Now we are going to add the code that takes the picture, saves it to Firebase Storage and then goes into the guest details and adds the URL to the image we saved.

It should be inside the .then() right after we run the Firebase transaction.

if (guestPicture != null) {
  const storageRef = firebase
    .storage()
    .ref(`/guestProfile/${newGuest.id}/profilePicture.png`);

  return storageRef
    .putString(guestPicture, 'base64', { contentType: 'image/png' })
    .then(() => {
      return storageRef.getDownloadURL().then(downloadURL => {
        return this.eventListRef
          .doc(eventId)
          .collection('guestList')
          .doc(newGuest.id)
          .update({ profilePicture: downloadURL });
      });
    });
}

We are creating a reference to our Firebase Storage:

guestProfile / guestId / profilePicture.png;

And that is where we store our file, to save it we use the .putString() method, and pass it the base64 string we got from the Camera Plugin.

Remember that you’d need to import the Storage module:

import * as firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
import 'firebase/storage';

After we upload the image to Firebase Storage we create a database reference to the guest we created and create a profilePicture property, and then we set that property to the picture’s download URL.

To test this on our phones, we must run it with capacitor, so we’re going to do two things right now.

First, open up your terminal and type

ionic build

It will generate a new build for your app, then type

ionic cap sync

And now you’re ready to test your app, type in the terminal

ionic cap open

It will prompt you for the OS you want to target, click it and it should either open XCode or Android Studio depending on your choice.

If you’re running Linux like myself, you’re probably going to need to manually open Android Studio and import the project from the options there.

Once Android Studio is open and the project is loaded you can click the green “play” button to run the app on your phone.

If you get a permission denied error from Firebase Storage, make sure you open the Storage tab inside your Firebase Console, click the “Get Started” button and set up the Storage portion of your app.

And that is it. Now you have a fully functional way of capturing photos with your camera and uploading them to Firebase Storage.

Next Steps

In the next lesson, we’re going to prepare our app to go public and we’ll dive into Security Rules.

See you in the next section!

Next Lesson: Handling Firebase Security Rules.