20

Problem

I am trying to create an app with react native and firebase. One of the features I would like for this app is the ability to upload images. I am having some trouble uploading the images to firebase storage though. I am using expo's image picker to find the path of the image that the user wants to upload, but once I have the path I don't know how to convert that to something I can upload to firebase.

Can somebody help me convert the path of an image to something I can upload to firebase storage with react native?

What I've tried

I tried using:

     _pickImage = async () => {
    let result = await ImagePicker.launchImageLibraryAsync({
      MediaTypeOptions: 'Images',
      quality: 0.4,
_uploadAsByteArray = async (pickerResultAsByteArray, progressCallback) => {

    try {

      var metadata = {
        contentType: 'image/jpeg',
      };

      var storageRef = firebase.storage().ref();
      var ref = storageRef.child('images/'+expoID+'/'+this.state.time)
      let uploadTask = ref.put(pickerResultAsByteArray, metadata)

      uploadTask.on('state_changed', function (snapshot) {

        progressCallback && progressCallback(snapshot.bytesTransferred / snapshot.totalBytes)

        var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        console.log('Upload is ' + progress + '% done');

      }, function (error) {
        console.log("in _uploadAsByteArray ", error)
      }, function () {
        var downloadURL = uploadTask.snapshot.downloadURL;
        console.log("_uploadAsByteArray ", uploadTask.snapshot.downloadURL)
    this.setState({imageUploaded:true})
      });


    } catch (ee) {
      console.log("when trying to load _uploadAsByteArray ", ee)
    }
  }


  convertToByteArray = (input) => {
    var binary_string = this.atob(input);
    var len = binary_string.length;
    var bytes = new Uint8Array(len);
    for (var i = 0; i < len; i++) {
      bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes
  }

  atob = (input) => {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';

    let str = input.replace(/=+$/, '');
    let output = '';

    if (str.length % 4 == 1) {
      throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
    }
    for (let bc = 0, bs = 0, buffer, i = 0;
      buffer = str.charAt(i++);

      ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
        bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
    ) {
      buffer = chars.indexOf(buffer);
    }

    return output;
  }


uploadImage(bsfdata){
    this.setState({imageUploaded:false})
    this._uploadAsByteArray(this.convertToByteArray(bsfdata), (progress) => {
    this.setState({ progress:progress })
    })
  }
  base64:true,
});



 /* if (!result.cancelled) {
      this.setState({ image: result.uri });
      let formData = new FormData();
        formData.append('photo', {
           uri,
           name: `photo.${fileType}`,
           type: `image/${fileType}`,
     });}*/
      this.uploadImage(result.base64);
  };

}

I've tried it with the commented code added, which doesn't upload anything, and I've tried it with how the code is now, which gives me the error Can currently only create a Blob from other Blobs, and the uploading progress never gets above 0%.

GIISE
  • 743
  • 3
  • 9
  • 24
  • 1
    may be [this](https://github.com/g6ling/React-Native-Tips/tree/master/How_to_upload_photo%2Cfile_in%20react-native) will help you. he is attaching image uri with it. – Tejas Pandya Jan 05 '18 at 07:13
  • Yes, as @TejasPandya says, you may want to reconsider building a blob altogether, although this may depend on your API set up – Liam Clark Gutiérrez Mar 02 '23 at 11:28

6 Answers6

14

If you are using expo (>=26), then you can do it easily with the following lines of code.

uploadImage = async(imageUri) => {
  const response = await fetch(imageUri);
  const blob = await response.blob();
  var ref =   firebase.storage().ref().child("image.jpg");
  return ref.put(blob);
}

Reference: https://youtu.be/KkZckepfm2Q

Sriteja Sugoor
  • 574
  • 3
  • 13
8

Only .blob() in react native do not work! You need a function for this

export const uriToBlob = (uri) => {
 return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.onload = function () {
      // return the blob
      resolve(xhr.response)
    }
    xhr.onerror = function () {
      reject(new Error('uriToBlob failed'))
    }
    xhr.responseType = 'blob'
    xhr.open('GET', uri, true)

    xhr.send(null)})}

And upload function to firebase:

export async function uploadFile(uri, filename, folder) {
  if (!filename) return
  const storageRef = ref(storage, `${folder}/${filename}`)
  const blobFile = await uriToBlob(uri)
  try {
    uploadBytes(storageRef, blobFile).then(async (snapshot) => {
      console.log('snapshot', snapshot)
      const url = await getDownloadURL(storageRef)
      return url
    })
  } catch (err) {
    console.log(err)
    return null
  }
}
Leffa
  • 369
  • 4
  • 7
  • This worked for me to fix the `Failed to construct 'Response': The status provided (0) is outside the range [200, 599]` issue when I used fetch for blob conversion. – FutureJJ Jul 25 '23 at 10:40
  • use the https://rnfirebase.io/ in react native. The experience is better and you not need to convert to blob – Leffa Jul 25 '23 at 13:19
  • Perfect work for me with AWS Amplify Storage – Imtee Aug 29 '23 at 23:55
2

Not sure whom this might help, but if you're using MediaLibrary to load images from the gallery, then the uri comes in the format of uri = file:///storage/emulated/0/DCIM/Camera/filename.jpg

In this case, using fetch(uri) didn't help me get the blob.

But if you use fetch(uri.replace("file:///","file:/")) and then follow @sriteja Sugoor's answer, you'll be able to upload the file blob.

KKM
  • 626
  • 5
  • 12
1

Refer this link - https://github.com/dailydrip/react-native-firebase-storage/blob/master/src/App.js#L43-L69

Following block of code is working fine.

uploadImage(uri, mime = 'application/octet-stream') {
    return new Promise((resolve, reject) => {
      const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri
      let uploadBlob = null

      const imageRef = FirebaseClient.storage().ref('images').child('image_001')

      fs.readFile(uploadUri, 'base64')
        .then((data) => {
          return Blob.build(data, { type: `${mime};BASE64` })
        })
        .then((blob) => {
          uploadBlob = blob
          return imageRef.put(blob, { contentType: mime })
        })
        .then(() => {
          uploadBlob.close()
          return imageRef.getDownloadURL()
        })
        .then((url) => {
          resolve(url)
        })
        .catch((error) => {
          reject(error)
      })
    })
  }
Nishant Mendiratta
  • 760
  • 13
  • 25
0

You need to install rn-fetch-blob module:

npm install --save rn-fetch-blob

Then, do the following:

import RNFetchBlob from 'rn-fetch-blob';

const Blob = RNFetchBlob.polyfill.Blob;
const fs = RNFetchBlob.fs;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
window.Blob = Blob;

function uploadImage(path) {
    const imageFile = RNFetchBlob.wrap(path);

    // 'path/to/image' is where you wish to put your image in
    // the database, if you would like to put it in the folder
    // 'subfolder' inside 'mainFolder' and name it 'myImage', just 
    // replace it with 'mainFolder/subfolder/myImage'
    const ref = firebase.storage().ref('path/to/image');
    var uploadBlob = null;

    Blob.build(imageFile, { type: 'image/jpg;' })
        .then((imageBlob) => {
            uploadBlob = imageBlob;
            return ref.put(imageBlob, { contentType: 'image/jpg' });
        })
        .then(() => {
            uploadBlob.close();
            return ref.getDownloadURL();
        })
        .((url) => {
            // do something with the url if you wish to
        })
        .catch(() => {
            dispatch({
                type: UPDATE_PROFILE_INFO_FAIL,
                payload: 'Unable to upload profile picture, please try again'
            });
        });
}

Please do ask if there's any part of the code that you don't understand. To upload multiple images, simply wrap this code with a for loop. Or if you want to make sure that every image is uploaded without any error, use Promise

Andrew Irwin
  • 691
  • 12
  • 40
K.Wu
  • 3,553
  • 6
  • 31
  • 55
  • 2
    just FYI, for anyone who wants to upload an image to firebase storage who is not using expo (Like I'm not), you can just pass the image uri as the first param to the ref.put() function. i.e ref.put(uri) – Andrew Irwin Sep 04 '18 at 09:51
  • @K.Wu . i am getting Attempt to invoke interface method 'java.lang.String.facebook.react.bridge.readablemap.getstring(java.lang.string) on a null object reference exception when i add "window.Blob = Blob;", if i remove this no exception but blob format is not correct . Please help – Jaffer Sathick Jan 21 '19 at 15:48
  • 1
    @AndrewIrwin perhaps it was possible when you wrote that comment, but now the put method requires a Blob or File. Passing the URI string will result in an error being thrown. – stevejboyer Jul 23 '19 at 22:10
  • 1
    What is `window` in this case? There is no native window object in react-native... – stevejboyer Jul 23 '19 at 22:28
  • It's missing "then" from Blob.build... `.((url) => {` – Coder828 May 02 '20 at 01:38
0
const Blob = RNFetchBlob.polyfill.Blob;
      const fs = RNFetchBlob.fs;
      let uploadBlob;
      await fs
        .readFile(params?.file.path, 'base64')
        .then((data) => {
          return Blob.build(data, {type: `BASE64`});
        })
        .then((blob) => {
          uploadBlob = blob;
          console.log(uploadBlob, 'uploadBlob');
        });
Ankit
  • 411
  • 4
  • 4