Smarticles101
Smarticles101

Reputation: 1926

React Native base64 Image Upload to Firebase Storage

I am currently working on an App. The workflow I currently have is fairly simple.

A user creates an account, and then is taken to a page to populate their profile information. Name, description, and a few images.

I use expo's ImagePicker to get the image:

    let result = await ImagePicker.launchImageLibraryAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      quality: 0.1,
      allowsEditing: true,
      aspect: [2, 3],
      base64: true
    });

Originally, I was using this to upload the images:

// Why are we using XMLHttpRequest? See:
    // https://github.com/expo/expo/issues/2402#issuecomment-443726662
    const blob = await new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.onload = function() {
        resolve(xhr.response);
      };
      xhr.onerror = function(e) {
        reject(new TypeError("Network request failed"));
      };
      xhr.responseType = "blob";
      xhr.open("GET", uri, true);
      xhr.send(null);
    });

    const ref = firebase
      .storage()
      .ref()
      .child(uuid.v4());
    const snapshot = await ref.put(blob);

    // We're done with the blob, close and release it
    blob.close();

    let url = await snapshot.ref.getDownloadURL();

    return url;

The problem here is I looped through that function about 6 times, and I kept getting some obscure error.

Currently, I am attempting to upload the images using this:

const ref = firebase
      .storage()
      .ref()
      .child(uuid.v4());

    const snapshot = await ref.putString(b64Url, "data_url");

This works well on web, but in the native app I get the error:

FirebaseStorageError {
  "code_": "storage/invalid-format",
  "message_": "Firebase Storage: String does not match format 'base64': Invalid character found",
  "name_": "FirebaseError",
  "serverResponse_": null,
}

The last comment on this issue outlines the problem. To break it down: atob doesn't exist. This is the sole problem behind the error. To fix, I polyfilled it like this:

import { decode, encode } from "base-64";

if (!global.btoa) {
  global.btoa = encode;
}

if (!global.atob) {
  global.atob = decode;
}

However, the second problem is that:

Firebase also tries to use the native Blob class (implemented by react-native), but the react-native version of Blob incorrectly converts the Uint8Array data to a string, corrupting the upload.

I tried his solution of deleteing global.Blob and restoring it after the upload. Firebase must have become dependent upon blob though, because now it errors out since Blob doesn't exist. Edit: Blob is actually being called somewhere in AppEntry.bundle, the uploading works correctly.

I would like to keep my app in a managed workflow, so I would very much prefer not to eject.

My questions are as follows:

  1. Where specifically in react-native is the broken Blob code that:

incorrectly converts the Uint8Array data to a string

  1. Is there a way that I can, while avoiding errors or ejecting, upload 6 images at once to firebase storage? If so, how?

Upvotes: 1

Views: 2117

Answers (1)

Smarticles101
Smarticles101

Reputation: 1926

The solution I ended up following was this:

  async function uploadImageAsync(uri) {
    const ref = firebase
      .storage()
      .ref()
      .child(uuid.v4());

    const blob = await new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.onload = function() {
        resolve(xhr.response);
      };
      xhr.onerror = function() {
        reject(new TypeError("Network request failed"));
      };
      xhr.responseType = "blob";
      xhr.open("GET", uri, true);
      xhr.send(null);
    });

    var mimeString = uri
      .split(",")[0]
      .split(":")[1]
      .split(";")[0];

    const snapshot = await ref.put(blob, { contentType: mimeString });

    let url = await snapshot.ref.getDownloadURL();

    return url;
  }

I found that I could not seem to get firebase's putString function to work, but I could create a blob out of the string using XMLHttpRequest. Then I just upload the blob to firebase

Upvotes: 2

Related Questions