How to make an image uploading app with Vue, Quasar, Firebase Storage and Cordova - Part 1
Tags: vue, cordova, firebase, images
Published: 2019-10-10 00:00:00

---
date: "2019-10-10"
title: "How to make an image uploading app with Vue, Quasar, Firebase Storage and Cordova - Part 1"
published: true
category: "vue"
tags: "vue, cordova, firebase, images"
canonical_url: "https://www.learningsomethingnew.com/how-to-make-an-image-uploading-app-with-vue-quasar-firebase-storage-and-cordova-part-1"
cover_image: "https://www.learningsomethingnew.com/vue-fb-image-upload/cover.jpg"
---

## What we're building

{% youtube Dhx1Wu6Gzu8 %}

We'll build a cross-platform mobile app for taking photos and uploading to firebase. 

In Part 1, we'll take a picture and save it to Firebase Cloud Storage, and then show it in our app.
In Part 2, we'll offload the uploading work, and use the blueimp library to generate a thumbnail locally and show it while uploading.

## The stack ##

* Vue JS - Component framework
* Cordova - Cross platform mobile framework
* Quasar - UI framework (and CLI)
* Firebase Cloud Storage - For storing the photos
* Web Workers - For offloading the uploading to a separate thread

## Scaffolding ##

We'll use [Quasar CLI](https://quasar.dev/quasar-cli/developing-cordova-apps/preparation) to initialize a new project, and run the cordova mode (android or ios) to see the app running on your connected device.

```sh
quasar create vue-firebase-image-upload
cd vue-firebase-image-upload
quasar dev -m android
```

> You'll have to add `https: true` in the `devServer` section of `quasar.conf.js` if running on android > 9

You should get a basic working version that looks like this:

![quasar-dev](https://www.learningsomethingnew.com/vue-fb-image-upload/quasar-dev.png)

This is a good time to make your first commit.

> For more details on how to install and setup quasar, see [this post](https://www.learningsomethingnew.com/how-to-build-a-sound-cloud-like-audio-player-app-with-vue-js-quasar-and-wave-surfer#scaffolding)


## Taking a picture and getting base64
The way to store images in Firebase Cloud Storage is by saving the base-64 string of the image, using Firebase's `putString` method.
Notice that you have to remove the base64 prefix from the string before uploading, or Firebase will reject the string.
To begin, we'll add a button that takes a picture using Cordova's camera plugin, and print the base64 string.

### Adding plugins
First, add cordova camera plugin and file plugin:

```sh
cd src-cordova
cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-camera
```

### Taking the picture

In order to take a picture, we'll add some code in a new service file `src/services/cordova-camera.js`:


```js
async function getCameraFileObject() {
  return new Promise((resolve, reject) => {

    let camera = navigator.camera;

    const options = {
      quality: 50,
      destinationType: camera.DestinationType.FILE_URI,
      encodingType: camera.EncodingType.JPG,
      mediaType: camera.MediaType.PICTURE,
      saveToPhotoAlbum: true,
      correctOrientation: true
    };

    camera.getPicture(imageURI => {
      window.resolveLocalFileSystemURL(imageURI,
        function (fileEntry) {
          fileEntry.file(
            function (fileObject) {
              resolve(fileObject)
            },
            function (err) {
              console.error(err);
              reject(err);
            }
          );
        },
        function () { }
      );
    },
      console.error,
      options
    );
  })

}

async function getBase64FromFileObject(fileObject) {
  return new Promise((resolve, reject) => {
    var reader = new FileReader()
    reader.onloadend = function (evt) {
      var image = new Image()
      image.onload = function (e) {
        resolve(evt.target.result)
      }
      image.src = evt.target.result
    }
    reader.readAsDataURL(fileObject)
  })
}

async function getBase64FromCamera() {
  let fileObject = await getCameraFileObject();
  let base64 = await getBase64FromFileObject(fileObject);
  return base64;
}

export default {
  getBase64FromCamera
}
```

This service is doing several steps:
1. Takes a picture using Cordova's camera plugin. This returns an image URI.
2. Gets a file *entry* using Cordova's file plugin, with `resolveLocalFileSystemURL` function.
3. Gets a File *object* using the `file` method.
4. Uses `FileReader` to get the base64 representation of the file.


Now let's add a simple event bus. Add the file `src/services/event-bus.js` with content:

```js
import Vue from 'vue';
export const EventBus = new Vue();
```

Now in `src/layouts/MyLayout.vue`, we'll add a button in the toolbar for taking a picture, and use our event bus to send the handling to `Index.vue`:

```html
<template>
  ...

  <q-btn flat dense round @click="takePicture">
    <q-icon name="camera" />
  </q-btn>

  ...
</template>

<script>
  import { EventBus } from "../services/event-bus.js";

  export default {
    ...

    methods: {
      takePicture() {
        EventBus.$emit('takePicture')
      }
    }
  }
  </script>
```

Finally we'll catch the `takePicture` event in our main component: `Index.vue`, call the `cordova-camera.js` service function and print the base64 result (which we'll later upload to firebase):

In `Index.vue` file in the `<script>` section:

```js
  import { EventBus } from "../services/event-bus";
  import cordovaCamera from "../services/cordova-camera";
  
  export default {
    name: "PageIndex",
    mounted() {
      EventBus.$off("takePicture");
      EventBus.$on("takePicture", this.uploadImageFromCamera);
    },
    methods: {
      async uploadImageFromCamera() {
        let base64 = await cordovaCamera.getBase64FromCamera();
        console.log("base64", base64)
      }
    }
  };
``` 

Running with `quasar dev -m android`, and looking at the chrome dev tools, we can see the base64 output as a long string printed in the console:

![base64](https://www.learningsomethingnew.com/vue-fb-image-upload/base64.png) 

## Adding firebase

### Web SDK or native SDK?

When using Firebase in a Cordova app, you can choose between using the Web SDK and a cordova plugin the wraps the Native SDKs. The state of the current Firebase cordova plugins seems a little unstable to me, not fully supporting Firebase Cloud Storage yet. And with the ability to offload work to a web worker, we can use the full-featured official Web SDK, and not take the performance hit.
Add the firebase sdk using yarn:

```sh
yarn add firebase
```

### Firebase setup

Follow the instructions in the Firebase Cloud Storage [setup page](https://firebase.google.com/docs/storage/web/start).
After the setup you should have a firebase project with Storage enabled. We'll save the settings in a separate file: `firebase-config.js` (this file won't be committed to our repo). The contents should look something like this:

```js
export default {
    firebase: {
        apiKey: '<your-api-key>',
        authDomain: '<your-auth-domain>',
        databaseURL: '<your-database-url>',
        storageBucket: '<your-storage-bucket-url>'
    }
}
```

## Uploading the image

Now that we have Firebase settings in place, we'll add another service for handling Firebase app initialization and uploading.
Add the file `src/services/cloud-storage.js` with content:

```js
import * as firebase from "firebase/app";
import 'firebase/storage';
import firebaseConfig from './firebase-config';

async function uploadBase64(imageData, storageId) {
    return new Promise((resolve, reject) => {
        let uploadTask = firebase.storage().ref().child(storageId).putString(imageData, "base64");
        uploadTask.on(
            "state_changed",
            function (snapshot) {},
            function (error) {
                reject(error)
            },
            function () {
                uploadTask.snapshot.ref
                    .getDownloadURL()
                    .then(function (downloadURL) {
                        console.log("Uploaded a blob or file!");
                        console.log("got downloadURL: ", downloadURL);
                        resolve(downloadURL);
                    });
            }
        );
    });
}

function initialize() {
    firebase.initializeApp(firebaseConfig.firebase) 
}

export default {
    uploadBase64,
    initialize
}
```

The `uploadBase64` function uploads `imageData` (the base64 string) using the `putString` method, under the Firebase child of `storageId`. You can have any ID you want, here I've chosen to use the unix datetime number as the child ID. After the uploading is done, we return the image URL using `getDownloadURL` function of the uploading task.

In our main `App.vue` file we'll call the initialize function when the app is mounted:
```js
import cloudStorage from './services/cloud-storage'
export default {
    name: 'App',
    mounted() {
      cloudStorage.initialize();
    }
}
```

Finally we'll call the uploading function from `Index.vue`. This is now the content of `Index.vue`:

```html
<template>
  <q-page>
    <div class="row justify-center q-ma-md" v-for="(pic, idx) in pics" :key="idx">
      <div class="col">
        <q-card>
          <q-img spinner-color="white" :src="pic" />
        </q-card>
      </div>
    </div>
  </q-page>
</template>

<script>
import { EventBus } from "../services/event-bus";
import cordovaCamera from "../services/cordova-camera";
import cloudStorage from "../services/cloud-storage";

export default {
  name: "PageIndex",
  data() {
    return {
      pics: []
    };
  },
  mounted() {
    EventBus.$off("takePicture");
    EventBus.$on("takePicture", this.uploadImageFromCamera);
  },
  methods: {
    removeBase64Prefix(base64Str) {
      return base64Str.substr(base64Str.indexOf(",") + 1);
    },

    async uploadImageFromCamera() {
      const base64 = await cordovaCamera.getBase64FromCamera();
      const imageData = this.removeBase64Prefix(base64);
      const storageId = new Date().getTime().toString();
      const uploadedPic = await cloudStorage.uploadBase64(imageData, storageId);
      this.pics.push(uploadedPic);
    }
  }
};
</script>
```

We've added the `pics` array to our component's `data`, containing URLs of all the pictures we uploaded. We've added a `v-for` that will show each picture in a quasar `q-img` component inside a `q-card` component.
We've also changes `uploadImageFromCamera` to take a picture, get the `base64` string (stripping the prefix so Firebase won't freak out), calculated the `storageId` using the current datetime, and called the uploading function. After it's uploaded, we add the resulting URL to the `pics` array.


And we can now see the uploaded image: 

![uploaded-image](https://www.learningsomethingnew.com/vue-fb-image-upload/image-upload.png)


The full code is on [GitHub](https://github.com/syonip/vue-firebase-image-upload).

In the next part of the series we'll move tasks to a web worker, add a loading spinner, and save the URLs in local storage. Stay tuned!