I'm trying to upload an image file to firebase storage, save the download URL, and load it after the upload is completed. When I run the app with debug js remotely on it works fine. When I turn off debug mode it stops working with the invalid format exception. The same happens when I run in a real device (both iOS and Android)
The base64 response data from React Native Image Picker seems to be correct
Here's my code
...
import * as ImagePicker from 'react-native-image-picker'; //0.26.10
import firebase from 'firebase'; //4.9.1
...
handleImagePicker = () => {
const { me } = this.props;
const options = {
title: 'Select pic',
storageOptions: {
skipBackup: true,
path: 'images'
},
mediaType: 'photo',
quality: 0.5,
};
ImagePicker.showImagePicker(options, async (response) => {
const storageRef = firebase.storage().ref(`/profile-images/user_${me.id}.jpg`);
const metadata = {
contentType: 'image/jpeg',
};
const task = storageRef.putString(response.data, 'base64', metadata);
return new Promise((resolve, reject) => {
task.on(
'state_changed',
(snapshot) => {
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
},
(error) =>
console.log(error),
() => {
this.onChangeProfileImage();
}
);
});
}
}
onChangeProfileImage = async () => {
const { me } = this.props;
const storageRef = firebase.storage().ref(`/profile-images/user_${me.id}.jpg`);
const profileImageUrl = await new Promise((resolve, reject) => {
storageRef.getDownloadURL()
.then((url) => {
resolve(url);
})
.catch((error) => {
console.log(error);
});
});
// some more logic to store profileImageUrl in the database
}
Any idea how to solve this?
Thanks in advance.
After some research and debug I found the cause of the issue and a solution for it.
Firebase uses atob
method to decode the base64 string sent by putstring
method.
However, since JavaScriptCore doesn't have a default support to atob
and btoa
, the base64 string can't be converted, so this exception is triggered.
When we run the app in debug javascript remotely mode, all javascript code is run under chrome environment, where atob
and btoa
are supported. That's why the code works when debug is on and doesn't when its off.
To handle atob
and btoa
in React Native, we should either write our own encode/decode method, or install a lib to handle it for us.
In my case I preferred to install base-64
lib
But here's an example of a encode/decode script:
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
const Base64 = {
btoa: (input:string = '') => {
let str = input;
let output = '';
for (let block = 0, charCode, i = 0, map = chars;
str.charAt(i | 0) || (map = '=', i % 1);
output += map.charAt(63 & block >> 8 - i % 1 * 8)) {
charCode = str.charCodeAt(i += 3/4);
if (charCode > 0xFF) {
throw new Error("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
}
block = block << 8 | charCode;
}
return output;
},
atob: (input:string = '') => {
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;
}
};
export default Base64;
Usage:
import Base64 from '[path to your script]';
const stringToEncode = 'xxxx';
Base64.btoa(scriptToEncode);
const stringToDecode = 'xxxx';
Base64.atob(stringToDecode);
After choosing either to use the custom script or the lib, now we must add the following code to the index.js
file:
import { decode, encode } from 'base-64';
if (!global.btoa) {
global.btoa = encode;
}
if (!global.atob) {
global.atob = decode;
}
AppRegistry.registerComponent(appName, () => App);
This will declare atob
and btoa
globally. So whenever in the app those functions are called, React Native will use the global scope to handle it, and then trigger the encode
and decode
methods from base-64
lib.
So this is the solution for Base64 issue.
However, after this is solved, I found another issue Firebase Storage: Max retry time for operation exceed. Please try again
when trying to upload larger images. It seems that firebase
has some limitation on support to React Native uploads, as this issue suggests.
I believe that react-native-firebase
may not struggle on this since it's already prepared to run natively, instead of using the web environment as firebase
does. I didn't test it yet to confirm, but it looks like this will be the best approach to handle it.
Hope this can be helpful for someone else.
The problem is now solved using fetch() API. The promise returned can be converted to blob which you can upload to firebase/storage
Here is an example
let storageRef = storage().ref();
let imageName = data.name + "image";
let imagesRef = storageRef.child(`images/${imageName}`);
const response = await fetch(image);
const blob = await response.blob(); // Here is the trick
imagesRef
.put(blob)
.then((snapshot) => {
console.log("uploaded an image.");
})
.catch((err) => console.log(err));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With