React Native base64 Image Upload to Firebase Storage - firebase

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:
Where specifically in react-native is the broken Blob code that:
incorrectly converts the Uint8Array data to a string
Is there a way that I can, while avoiding errors or ejecting, upload 6 images at once to firebase storage? If so, how?

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

Related

firebase storage image upload fails the first time, but subsequent upload attempts load

so, I'm new to react native and firebase storage and I am trying to upload images to storage which fails the first time but if I try immediately afterwards it works with no issues at all and can see the photo in the storage.
The error I am getting on the first attempt is:
{"code":"storage/unknown","customData":{},"name":"FirebaseError","status_":400,"_baseMessage":"Firebase Storage: An unknown error occurred, please check the error payload for server response. (storage/unknown)","line":20330,"column":28,"sourceURL":"http://192.168.1.12:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false&strict=false&minify=false"}
This is the code below:
const submitPhoto = async () => {
let id = await auth.currentUser.uid;
// Create file metadata including the user id
var metadata = {
customMetadata: {
userUpload: id,
},
};
var ref;
try {
const response = await fetch(image);
const blob = await response.blob();
let imageName = image.substring(image.lastIndexOf('/') + 1);
setFileName(imageName);
ref = await firebase
.storage()
.ref('climbPhotos')
.child(filename)
.put(blob, metadata);
console.log(ref.metadata.name);
console.log(ref.metadata);
} catch (error) {
console.log(JSON.stringify(error));
Alert.alert('Server Error', 'Error has occurred on image upload');
}
setImage(null);
};
I am at a lost with this as like I said it occurs only on the first attempt of me opening that particular screen and uploading any image for the first time.
So I figured out what the issue was, and it's all to do with the upload using state values for the filename apparently even though the value was being set (i checked with console logs immediately after being set) the value wasn't always being passed correctly. whether this was the issue or not it seems my issue has been solved by removing states from the child.
const response = await fetch(image);
const blob = await response.blob();
let imageName = image.substring(image.lastIndexOf('/') + 1);
setFileName(imageName);
console.log(' vvvv ');
console.log(filename);
console.log(imageName);
console.log(image);
console.log(' ^^^ ');
var imageRef = firebase.storage().ref('climbPhotos').child(imageName);
26a51e0a-8ab8-4b62-aa4a-46a3d777e4fa.jpeg
file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252FRockClimbingApp-cde1c400-dff9-44c8-8840-c83b731c56de/ImagePicker/26a51e0a-8ab8-4b62-aa4a-46a3d777e4fa.jpeg
as you can see the log shows the image and imageName but not the filename due to states being async

How can i transfer a temporary Taken image uri into and permanent uri to store it in a server ? REACT NATIVE

i started working with expo image picker and before i was storing the uri of the image in the FileSystem.documentDirectory from expo-file-system , that would store it in the phone , but now i want to store it on my database server (firebase) i don't know how to transfer it to a permanent uri ? how can i manage that ?
this the temporary uri that i got after taking the image : file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252Fshop-8054a9d3-c78c-47df-81bd-20de56fa4bd4/ImagePicker/5a4bab1f-8f7c-4ffa-ad42-f04ea740e429.jpg
Firebase'Storage accepts BLOB data for file upload.
Fetch and upload BLOB data to cloud storage
const imgRef = firebase.storage().ref("images/filename.jpg");
const metadata = { contentType: "image/jpg" };
const localPath="file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540anonymous%252Fshop-8054a9d3-c78c-47df-81bd-20de56fa4bd4/ImagePicker/5a4bab1f-8f7c-4ffa-ad42-f04ea740e429.jpg"
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", localPath, true);
xhr.send(null);
});
// Firebase API should change while you're working with Firebase V9
await imgRef.put(blob, metadata);
// We're done with the blob, close and release it
blob.close();

Expo/Firebase: Image chosen from camera roll uploading as octet-stream instead of .jpg

I've been having trouble viewing the image files I've uploaded to firebase and just noticed the issue is with the file type in firebase.
Two files in my firebase storage console. One uploaded from my IOS simulator (octet-stream) and the other uploaded directly into the console from the browser which uploads properly and is viewable.
Here are my select and upload functions:
_selectPhoto = async () => {
const status = await getPermission(Permissions.CAMERA_ROLL);
if (status) {
let imageName = "pic"
const result = await ImagePicker.launchImageLibraryAsync(options);
if (!result.cancelled) {
Animated.timing(this.animatedWidth, {
toValue: 600,
duration: 15000
}).start()
this.uploadImage(result.uri, imageName)
.then(() => {
this.props.navigation.navigate('Profile')
})
.catch((error) => {
Alert.alert('Must Sign In');
this.props.navigation.navigate('Login')
console.log(error);
})
}
}
};
uploadImage = async (uri, imageName) => {
const user = firebase.auth().currentUser;
const response = await fetch(uri);
const blob = await response.blob();
let storageRef = firebase.storage().ref().child(''images/'+user.displayName+'/'+imageName+'.jpg'');
const snapshot = await storageRef.put(blob);
blob.close();
snapshot.ref.getDownloadURL().then(function(downloadURL) {
console.log("File available at", downloadURL);
user.updateProfile({
photoURL: downloadURL.toString(),
}).then(function() {
console.log('saved photo')
}).catch(function(error) {
console.log('failed photo')
});
});
}
When I get the link in my console, it also has the media&token:
... .appspot.com/o/profile-pic.jpg?alt=media&token=56eb9c36-b5cd-4dbb-bec1-3ea5c3a74bdd
If I CMD+Click in VS Code I receive an error:
{
error: {
code: 400,
message: "Invalid HTTP method/URL pair."
}
}
So naturally, when I put that link in the browser it downloads a file with that name but says:
The file “pic.jpg” could not be opened.
It may be damaged or use a
file format that Preview doesn’t recognize.
Maybe it could be something with mediaTypes, but I'm not exactly sure how to use it.
mediaTypes : String -- Choose what type of media to pick. Usage:
ImagePicker.MediaTypeOptions., where is one of: Images,
Videos, All.
Thanks!
I've been fighting with this same issue for the past few days. I was finally able get images to upload and render as expected by following the Firebase Upload example in the Expo repo. I don't fully understand why it works, but it seems like Firebase doesn't like the blob that's generated by
const blob = await response.blob();
Try replacing the above with:
const blob = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(xhr.response);
};
xhr.onerror = function(e) {
console.log(e);
reject(new TypeError('Network request failed'));
};
xhr.responseType = 'blob';
xhr.open('GET', uri, true);
xhr.send(null);
});

Firebase storage upload string with Cloud function

I would like to upload a string of text and have that string uploaded to Cloud storage. I've build it in plain JS, but having issues hacking it into a cloud function.
function download(exportObj){
var databuk = gcs.bucket('******.appspot.com');
// var bucket = admin.storage().bucket();
//var tocfileloc = storageRef.child('toctest.json');
// const name = "toctest.json";
// const bucketdes = bucket.name;
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj));
databuk.putString(dataStr, 'data_url').then(snapshot => {
console.log('Uploaded a data_url string!');
return true;
}).catch(err=>{
console.log("error",err);
})
}
I have some code above! The string is "exportObj"
You'll want to use the Admin SDK for this. It'll be something along the lines of:
const admin = require('firebase-admin');
admin.initializeApp();
// ... then later, in your function
const file = admin.storage().bucket().file('path/to/your/file.txt');
return file.save('This will get stored in my storage bucket.', {
gzip: true,
contentType: 'text/plain'
}).then(() => {
console.log('all done!');
});
The specific "save" method is documented here.

Uploading Image to Firebase in React Native

I have two image paths in my component state
I try to upload one of the images inside of a function but get an error:
Firebase Storage: Invalid argument in 'put' at index 0: Expected Blob or file
and my function
submitImages = () => {
// Upload images to Firebase storage
let user = firebaseAuth.currentUser;
let imagesRef = storageRef.child('productImages/' + user.uid);
imagesRef.put(this.state.imageFront).then(snapshot => {
console.log('Uploaded ' + this.state.imageFront);
});
}
What should I be doing instead to get these images up to Firebase. Thanks!
What the error says is that you need to use a blob. You can use react-native-fetch-blob: https://github.com/wkh237/react-native-fetch-blob
Check out this example: https://github.com/dailydrip/react-native-firebase-storage/blob/master/src/App.js#L43-L69
I am posting my code since this was a bit frustrating for me:
To upload images to firebase.storage you need to upload the images as Blobs. If you don't know what Blobs are, don't worry: BLOB stands for Binary Large OBject.
Step 1.
npm install --save react-native-fetch-blob
Step 2.
// copy and paste this code where you will handle the file upload
import RNFetchBlob from 'react-native-fetch-blob'
const Blob = RNFetchBlob.polyfill.Blob;
const fs = RNFetchBlob.fs;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
window.Blob = Blob;
Step 3.
// The uploadImage function that you are going to use:
function uploadImage(uri, mime = 'image/jpeg', name) {
return new Promise((resolve, reject) => {
let imgUri = uri; let uploadBlob = null;
const uploadUri = Platform.OS === 'ios' ? imgUri.replace('file://', '') : imgUri;
const { currentUser } = firebase.auth();
const imageRef = firebase.storage().ref(`/jobs/${currentUser.uid}`)
fs.readFile(uploadUri, 'base64')
.then(data => {
return Blob.build(data, { type: `${mime};BASE64` });
})
.then(blob => {
uploadBlob = blob;
return imageRef.put(blob, { contentType: mime, name: name });
})
.then(() => {
uploadBlob.close()
return imageRef.getDownloadURL();
})
.then(url => {
resolve(url);
})
.catch(error => {
reject(error)
})
})
}
So how do you call this function?
Pass the URI of the image as the first argument. In my case img1, img2, img3 where variables that pointed to the URIs of the images, that I wanted to upload which were on my phone. They looked something like '/Phone/Pics/imageToUpload.jpeg', etc.
As the second argument you can pass 'image/jpeg' and the last argument is the name that you want to give the image. Chose the name that you like.
But what if I have several images and want to upload them and want to handle the upload correctly. What if one upload succeeds and the other does not?
Do this then:
let imgPromises = [];
imgPromises.push(uploadImage(img1, 'image/jpeg', 'imageOne'));
imgPromises.push(uploadImage(img2, 'image/jpeg', 'imageTwo'));
imgPromises.push(uploadImage(img3, 'image/jpeg', 'imageOne'));
Promise.all(imgPromises).then(urls => {
// ALL IMAGES SUCCEEDED and you will get an array of URIS that you can save to your database for later use!
}).catch(error => {
// One OR many images failed the upload. Give feedback to someone.
})
You can use react-native-firebase to upload image to storge https://rnfirebase.io/
const storage = firebase.storage();
const sessionId = new Date().getTime();
const imageRef = storage.ref('images').child(`${sessionId}`);
return imageRef.putFile(uri);
So far this is the best method I found to upload a file/image to a Firebase Storage with React Native. This method does not use any third party libraries except for the Expo SDK.
Get the File URI of the image to upload. To do this we will need to use Expo ImagePicker. The best place to include this code block is on to a button with an onPress handler.
ImagePicker.launchImageLibraryAsync({
mediaTypes: "Images"
}).then((result)=>{
if (!result.cancelled) {
// User picked an image
const {height, width, type, uri} = result;
return uriToBlob(uri); // will follow later
}
})
Generate a BLOB from the image URI. There are a lot of third party libraries to help do this. But if you don't want to install a library, then you can use XMLHttpRequest. The React Native docs recommends we use the Fetch API, but right now we can't use it because it will throw an error that we can only fetch https:// urls, but our URI is a file://. There is a way to get pass this, but using XMLHttpRequest will make things a lot simpler.
uriToBlob = (uri) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function() {
// return the blob
resolve(xhr.response);
};
xhr.onerror = function() {
// something went wrong
reject(new Error('uriToBlob failed'));
};
// this helps us get a blob
xhr.responseType = 'blob';
xhr.open('GET', uri, true);
xhr.send(null);
});
}
We have our BLOB, let's upload it to Firebase. This part is pretty straightforward as explained in the Firebase Docs.
uploadToFirebase = (blob) => {
return new Promise((resolve, reject)=>{
var storageRef = firebase.storage().ref();
storageRef.child('uploads/photo.jpg').put(blob, {
contentType: 'image/jpeg'
}).then((snapshot)=>{
blob.close(); // let's free up the blob
resolve(snapshot);
}).catch((error)=>{
reject(error);
});
});
}
That's it, you can now upload a file to Firebase Storage. The key part to this is getting a File URI and converting it to a BLOB. You can read more about this method here.
For some time I used the Firebase JS SDK with React Native. Using this library, as referred in this thread you need to use a library like rn-fetch-blob (react-native-fetch-blob is not maintained anymore) in order to provide a blob to Firebase Storage put() method.
Recently I started using React Native Firebase. As they say in their website "Using the native Firebase SDKs with React Native Firebase allows you to consume device SDKs which don't exist on the Firebase JS SDK".
Using React-Native-Firebase you don't need any extra library to upload images to Firebase Storage, and your code gets much cleaner:
export const uploadImage = (path, mime = 'application/octet-stream') => {
return new Promise((resolve, reject) => {
const imageRef = firebase.storage().ref('images').child('filename.jpg');
return imageRef.put(path, { contentType: mime })
.then(() => {
return imageRef.getDownloadURL();
})
.then(url => {
resolve(url);
})
.catch(error => {
reject(error);
console.log('Error uploading image: ', error);
});
});
};
if you don’t mind using cloudinary, I show how to upload and then get the uploaded url to save to firebase
https://medium.com/#ifeoluwaking24/how-to-upload-an-image-in-expo-react-native-to-firebase-using-cloudinary-24aac981c87
Also you can try it snack but make sure you add your cloud_name and upload_preset
https://snack.expo.io/#ifeking/upload-to-cloudinary

Resources