So I am very new to the whole coding scene and am trying to learn how to code using react native. Right now, I'm trying to figure out how to upload images using firebase (functions)and google cloud storage.
Below is the backend code that enables me to upload one image per submission to firebase.
I was wondering is it possible to modify this code so that it can upload multiple images per submission? If so, how would I go about doing it?
exports.storeImage = functions.https.onRequest((request, response) => {
return cors(request, response, () => {
const body = JSON.parse(request.body);
fs.writeFileSync("/tmp/uploaded-image.jpg", body.image, "base64", err => {
console.log(err);
return response.status(500).json({ error: err });
});
const bucket = gcs.bucket("myapp.appspot.com");
const uuid = UUID();
return bucket.upload(
"/tmp/uploaded-image.jpg",
{
uploadType: "media",
destination: "/places/" + uuid + ".jpg",
metadata: {
metadata: {
contentType: "image/jpeg",
firebaseStorageDownloadTokens: uuid
}
}
},
(err, file) => {
if (!err) {
return response.status(201).json({
imageUrl:
"https://firebasestorage.googleapis.com/v0/b/" +
bucket.name +
"/o/" +
encodeURIComponent(file.name) +
"?alt=media&token=" +
uuid,
imagePath: "/places/" + uuid + ".jpg"
});
} else {
console.log(err);
return response.status(500).json({ error: err });
}
}
);
})
.catch(error => {
console.log("Token is invalid!");
response.status(403).json({error: "Unauthorized"});
});
});
});
I don't have a React Native environment easily available, but I believe you can do it from the client with code like this:
await firebase.storage().ref('test/test.jpg').putFile('/path/to/test.jpg');
let downloadUrl = await firebase.storage().ref('test/test.jpg').getDownloadURL();
console.log('downloadUrl :', downloadUrl); // do whatever you need with it
To upload another image you just call the code twice, you can even do it in concurrently if you want.
When you use Firebase you should do most of the operations directly from the client, so you just need backend (including cloud functions) code if you need to do some heavy processing, use the admin SDK, integrate with third party apps, or stuff like that. For simple database or storage operations the client will suit you much better.
Also, you don't need to compose the download URL yourself, getDownloadUrl() does that for you. And if you access storage from the client it automatically integrates with Firebase Auth so you can protect your data.
Related
I was doing some POC on firebase cloud functions and made a CF with below snippet (This is working code snippet).
app.post('/create-pdf', (req, res) => {
pdfPromise.toFile( os.tmpdir() + '/template.pdf', (err, data) => {
if(err) {
console.log('Error Saving File', err);
res.send(Promise.reject());
}
res.send(Promise.resolve());
});
})
app.get('/get-pdf', (req, res) => {
res.sendFile(`${os.tmpdir()}/template.pdf`);
})
And the call to above api will be like this.
axios.post(url+'/create-pdf', { data : poBody }).then((res) => {
}).then(() => {
axios.get(url+'/get-pdf', { responseType: 'blob' }).then(res => {
const pdfBlob = new Blob([res.data], { type: 'application/pdf' });
saveAs(pdfBlob, 'payout.pdf')
})
})
This code is working fine..
I just want to know if server side code is running on GCP as CF, then do we need to clear the memory consumed by os.tmpdir(), or will it be cleared automatically?
Yes, you do need to delete the temporary files created in the temporary directory (which is an in-memory filesystem), because "files that you write consume memory available to your function, and sometimes persist between invocations".
There is a specific doc section and a video about that: https://firebase.google.com/docs/functions/tips#always_delete_temporary_files and https://www.youtube.com/watch?v=2mjfI0FYP7Y&feature=youtu.be
I have created a callable Cloud Function to read data from Firebase and send back the results to the client, however, only "null" is being returned to the client.
exports.user_get = functions.https.onCall((data, context) => {
if (context.auth && data) {
return admin.firestore().doc("users/" + context.auth.uid).get()
.then(function (doc) {
return { doc.data() };
})
.catch(function (error) {
console.log(error);
return error;
})
} return
});
I just reproduced your case connecting from a Cloud Function with a Firestore database and retriving data. As I can see you are trying to access the field in a wrong way when you are using "users/" + context.auth.uid, the method can't find the field so its returning a null value.
I just followed this Quickstart using a server client library documentation to populate a Firestore database and make a Get from it with node.js.
After that i followed this Deploying from GCP Console documentation in order to deploy a HTTP triggered Cloud Function with the following function
exports.helloWorld = (req, res) => {
firestore.collection('users').get()
.then((snapshot) => {
snapshot.forEach((doc) => {
console.log(doc.id, '=>', doc.data().born);
let ans = {
date : doc.data().born
};
res.status(200).send(ans);
});
})
And this is returning the desired field.
You can take a look of my entire example code here
This is because you are making a query from a database firestore, however the cloud support team has made it very cool to protect your applications from data leakages and so in a callable function as the name suggest you can only return data you passed to the same callable function through the data parameter and nothing else. if you try to access a database i suggest you use an onRequest Function and use the endpoint to get you data. that way you not only protect your database but avoid data and memory leakage.
examples of what you can return from a callable function
exports.sayHello = functions.https.onCall((data, context) => {
const name = data.name;
console.log(`hello ${name}`);
return `It was really fun working with you ${name}`;
});
first create a function in your index.js file and accept data through the data parameter but as i said you can only return data you passed through the data parameter.
now call the function
this is in the frontend code (attach an event listener to a button or something and trigger it
/* jsut say hello from firebase */
callButton.addEventListener('click', () => {
const sayHello = firebase.functions().httpsCallable('getAllUsers');
sayHello().then(resutls => {
console.log("users >>> ", resutls);
});
});
you can get your data using an onRequest like so
/* get users */
exports.getAllUsers = functions.https.onRequest((request, response) => {
cors(request, response, () => {
const data = admin.firestore().collection("users");
const users = [];
data.get().then((snapshot) => {
snapshot.docs.forEach((doc) => {
users.push(doc.data());
});
return response.status(200).send(users);
});
});
});
using a fetch() in your frontend code to get the response of the new onRequest function you can get the endpoint to the function in your firebase console dashboard.
but not that to hit the endpoint from your frontend code you need to add cors to your firebase cloud functions to allow access to the endpoint.
you can do that by just adding this line to the top of your index.js file of the firebase functions directory
const cors = require("cors")({origin: true});
I am exploring Cypress for e2e testing, looks like great software.
The problem is Authentication, the Cypress documentation explains why using the UI is very bad here.
So I tried looking at the network tap of my application, to see if I could create a POST request to the firebase API, and authenticate without using the GUI. But I can see that there at least 2 request fired, and token saved to application storage.
So what approach should I use?
Authenticate with the UI of my application, and instruct Cypress not to touch the local storage
Keep experimenting with a way of sending the correct POST requests, and save the values to local storage.
Make Cypress run custom JS code, and then use the Firebase SDK to login.
I am really looking for some advice here :)
When doing this myself I made custom commands (like cy.login for auth then cy.callRtdb and cy.callFirestore for verifying data). After getting tired of repeating the logic it took to build them, I wrapped it up into a library called cypress-firebase. It includes custom commands and a cli to generate a custom auth token.
Setup mostly just consists of adding the custom commands in cypress/support/commands.js:
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';
import 'firebase/firestore';
import { attachCustomCommands } from 'cypress-firebase';
const fbConfig = {
// Your config from Firebase Console
};
window.fbInstance = firebase.initializeApp(fbConfig);
attachCustomCommands({ Cypress, cy, firebase })
And adding the plugin to cypress/plugins/index.js:
const cypressFirebasePlugin = require('cypress-firebase').plugin
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// Return extended config (with settings from .firebaserc)
return cypressFirebasePlugin(config)
}
But there full details on setup are available in the setup docs.
Disclosure, I am the author of cypress-firebase, which is the whole answer.
I took the approach of using automated UI to obtain the contents of localStorage used by Firebase JS SDK. I also wanted to do this only once per whole Cypress run so I did it before the Cypress start.
Obtain Firebase SDK localStorage entry via pupeteer
Store the contents in the tmp file (problems passing it via env var to Cypress)
Pass the file location to Cypress via env var and let it read the contents and set the localStorage to setup the session
Helper script which obtains contents of localStorage:
const puppeteer = require('puppeteer')
const invokeLogin = async page => {
await page.goto('http://localhost:3000/login')
await page.waitForSelector('.btn-googleplus')
await page.evaluate(() =>
document.querySelector('.btn-googleplus').click())
}
const doLogin = async (page, {username, password}) => {
// Username step
await page.waitForSelector('#identifierId')
await page.evaluate((username) => {
document.querySelector('#identifierId').value = username
document.querySelector('#identifierNext').click()
}, username)
// Password step
await page.waitForSelector('#passwordNext')
await page.evaluate(password =>
setTimeout(() => {
document.querySelector('input[type=password]').value = password
document.querySelector('#passwordNext').click()
}, 3000) // Wait 3 second to next phase to init (couldn't find better way)
, password)
}
const extractStorageEntry = async page =>
page.evaluate(() => {
for (let key in localStorage) {
if (key.startsWith('firebase'))
return {key, value: localStorage[key]}
}
})
const waitForApp = async page => {
await page.waitForSelector('#app')
}
const main = async (credentials, cfg) => {
const browser = await puppeteer.launch(cfg)
const page = await browser.newPage()
await invokeLogin(page)
await doLogin(page, credentials)
await waitForApp(page)
const entry = await extractStorageEntry(page)
console.log(JSON.stringify(entry))
await browser.close()
}
const username = process.argv[2]
const password = process.argv[3]
main({username, password}, {
headless: true // Set to false for debugging
})
Since there were problem with sending JSON as environment variables to Cypress I use tmp file to pass the data between the script and the Cypress process.
node test/getFbAuthEntry ${USER} ${PASSWORD} > test/tmp/fbAuth.json
cypress open --env FB_AUTH_FILE=test/tmp/fbAuth.json
In Cypress I read it from the file system and set it to the localStorage
const setFbAuth = () =>
cy.readFile(Cypress.env('FB_AUTH_FILE'))
.then(fbAuth => {
const {key, value} = fbAuth
localStorage[key] = value
})
describe('an app something', () => {
it('does stuff', () => {
setFbAuth()
cy.viewport(1300, 800)
...
This is certainly a hack but to get around the login part for the app I am working on I use the beforeEach hook to login to the application.
beforeEach(() => {
cy.resetTestDatabase().then(() => {
cy.setupTestDatabase();
});
});
Which is derived from my helper functions.
Cypress.Commands.add('login', () => {
return firebase
.auth()
.signInWithEmailAndPassword(Cypress.env('USER_EMAIL'), Cypress.env('USER_PASSWORD'));
});
Cypress.Commands.add('resetTestDatabase', () => {
return cy.login().then(() => {
firebase
.database()
.ref(DEFAULT_CATEGORIES_PATH)
.once('value')
.then(snapshot => {
const defaultCategories = snapshot.val();
const updates = {};
updates[TEST_CATEGORIES_PATH] = defaultCategories;
updates[TEST_EVENTS_PATH] = null;
updates[TEST_STATE_PATH] = null;
updates[TEST_EXPORT_PATH] = null;
return firebase
.database()
.ref()
.update(updates);
});
});
});
What I would like to know is how the information coming back from firebase ultimately gets saved to localStorage. I don't really have an answer to this but it works. Also, the app uses .signInWithPopup(new firebase.auth.GoogleAuthProvider()) whereas above it signs in with email and password. So I am kind of shortcutting the signin process only because cypress has the CORS limitation.
This is becoming way easier with the upcoming Auth emulator. This has become easier with the Firebase Auth Emulator (firebase-tools >= 8.1.4).
cypress/support/signAs.js:
Cypress.Commands.add('signAs', (uid, opt) => {
cy.visit('/')
cy.window().its('firebase').then( fb => {
cy.wrap( (async _ => {
// Create a user based on the provided token (only '.uid' is used by Firebase)
await fb.auth().signInWithCustomToken( JSON.stringify({ uid }) );
// Set '.displayName', '.photoURL'; for email and password, other functions exist (not implemented)
await fb.auth().currentUser.updateProfile(opt);
})() )
})
})
Use it as:
cy.signAs('joe', { displayName: 'Joe D.', photoURL: 'http://some' });
If you need to set .email or .password, there are similar functions for those, but this was sufficient for my tests. I can now impersonate any user ad-hoc, as part of the test. The approach does not need users to be created in the emulator; you can just claim to be one, with the particular uid. Works well for me.
Note:
Firebase authentication is in IndexedDB (as mentioned in other answers) and Cypress does not clear it, between the tests. There is discussion about this in cypress #1208.
At the time writing, I've examined these approaches
stubbing firebase network requests - really difficult. A bunch of firebase requests is sent continuously. There are so many request params & large payload and they're unreadable.
localStorage injection - as same as request stubbing. It requires an internally thorough understanding of both firebase SDK and data structure.
cypress-firebase plugin - it's not matured enough and lack of documentation. I skipped this option because it needs a service account (admin key). The project I'm working on is opensource and there are many contributors. It's hard to share the key without including it in the source control.
Eventually, I implemented it on my own which is quite simple. Most importantly, it doesn't require any confidential firebase credentials. Basically, it's done by
initialize another firebase instance within Cypress
use that firebase instance to build a Cypress custom command to login
const fbConfig = {
apiKey: `your api key`, // AIzaSyDAxS_7M780mI3_tlwnAvpbaqRsQPlmp64
authDomain: `your auth domain`, // onearmy-test-ci.firebaseapp.com
projectId: `your project id`, // onearmy-test-ci
}
firebase.initializeApp(fbConfig)
const attachCustomCommands = (
Cypress,
{ auth, firestore }: typeof firebase,
) => {
let currentUser: null | firebase.User = null
auth().onAuthStateChanged(user => {
currentUser = user
})
Cypress.Commands.add('login', (email, password) => {
Cypress.log({
displayName: 'login',
consoleProps: () => {
return { email, password }
},
})
return auth().signInWithEmailAndPassword(email, password)
})
Cypress.Commands.add('logout', () => {
const userInfo = currentUser ? currentUser.email : 'Not login yet - Skipped'
Cypress.log({
displayName: 'logout',
consoleProps: () => {
return { currentUser: userInfo }
},
})
return auth().signOut()
})
}
attachCustomCommands(Cypress, firebase)
Here is the commit that has all integration code https://github.com/ONEARMY/community-platform/commit/b441699c856c6aeedb8b73464c05fce542e9ead1
Ok after much trial and error, I tried solution path 2 and it worked.
So my auth flow looks like this:
Send POST request (using cybress.request) to
https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword,
and parse the response. Create an object: response1 = response.body
Send POST request (using cybress.request) to
https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo,
use the idToken from the prev request. Create an object: user = response2.body.users[0];
Combine the response in an object, with the following properties:
const authObject = {
uid: response1.localId,
displayName: response1.displayName,
photoURL: null,
email: response1.email,
phoneNumber: null,
isAnonymous: false,
providerData: [
{
uid: response1.email,
displayName: response1.displayName,
photoURL: null,
email: body.email,
phoneNumber: null,
providerId: 'password'
}
],
'apiKey': apiKey,
'appName': '[DEFAULT]',
'authDomain': '<name of firebase domain>',
'stsTokenManager': {
'apiKey': apiKey,
'refreshToken': response1.refreshToken,
'accessToken': response1.idToken,
'expirationTime': user.lastLoginAt + Number(response1.expiresIn)
},
'redirectEventId': null,
'lastLoginAt': user.lastLoginAt,
'createdAt': user.createdAt
};
Then in cybress, I simply save this object in local storag, in the before hook: localStorage.setItem(firebase:authUser:${apiKey}:[DEFAULT], authObject);
Maybe not perfect, but it solves the problem. Let me know if you interested in the code, and if you have any knowledge about how to build the "authObject", or solve this problem in another way.
Using firebase admin to retrieve data from a collection in a cloud function fails for large items. Sample code i am using to query the selection from the cloud function is as follow
admin.database().orderByChild('mmyyyy').equalTo(month).once('value');
this call fails when i try to retrieve 10600 items (trying to figure out why). in google console there is this log but nothing else that can point me in the right direction
textPayload: "Function execution took 18547 ms, finished with status: 'response error'"
After many failed attempt, i decided to try to execute this call on the client using firebase sdk as follow:
result = await firebase.database().ref(`transactions`).orderByChild('mmyyyy').equalTo(month).once('value');
this works perfectly on the client without error and returning all my items 17000 of them (size of this json is 26MB).
Why is this the case? is there any limitation that is not documented?
Note:
I increased my cloud function memory to 1gb and timeout to 5min, didn't help.
Here is full sample code
const admin = require('firebase-admin');
var functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
const cors = require('cors')({
"origin": "*",
"methods": "POST,GET",
"allowedHeaders": "Content-Type,uid,agence,month,course,raceType,raceNumber,status",
"preflightContinue": false,
"optionsSuccessStatus": 204
});
function _findTransactions(agence, month, course, raceType, raceNumber, status) {
return new Promise((resolve, reject) => {
try {
let db = admin.database();
let findPromise = db.ref(`transactions`).orderByChild('mmyyyy').equalTo(month).once('value');
findPromise.then((result) => {
let transactions = result.val();
//removed business logic
resolve(transactions);
}).catch((err) => {
console.log(err);
reject(err);
});
} catch (error) {
console.log(error);
reject(error);
}
});
}
exports.findTransactions = functions.https.onRequest((req, res) => {
let uid;
try {
cors(req, res, () => {
uid = req.headers.uid;
let agence = req.headers.agence;
let month = req.headers.month;
let course = req.headers.course;
let raceType = req.headers.raceType;
let raceNumber = req.headers.raceNumber;
let status = req.headers.status;
if (req.method !== 'GET') {
return handleResponse(req, res, 403);
}
if (!uid || uid == null || uid == undefined) {
return handleResponse(req, res, 401);
}
_validateUserId(uid, ['central_cashier', 'admin'])
.then(() => {
_findTransactions(agence, month, course, raceType, raceNumber, status)
.then((result) => {
return handleResponse(req, res, 200, result);
}).catch((error) => {
return handleResponse(req, res, 500);
});
}).catch((error) => {
return handleResponse(req, res, 401);
});
});
} catch (error) {
return handleError(res, uid, error);
}
});
Your payload is too large and is exceeding the quota for Google Cloud Functions as you stated.
Two options comes to mind:
Compress the payload. Gzip the file before sending it to the client. This is easy with the NodeJS built in Zlib module, or;
Set up a virtual machine. Virtual machines are not bound to these restrictions.
I did some testing and conclude that Google Cloud Functions (GCF) is enforcing some kind of timeout or "abort" action when a query results in a large number of results (ie. many Datastore entities). See my comments attached to this question for some background.
tl;dr I created my own Express.js webserver and ran my GCF code on it.
This is how I tested it: I created an ubuntu instance with http/https and the Datastore API enabled. On my instance, I installed Node, Express, and got a basic https server working (self-signed certificate worked fine since this is just testing an api backend service). Then I copy-pasted my GCF code (the function that was failing in GCF) into my minimal Express webserver. I pointed my React app to use my instance, which triggered a query that resulted in over 32,000 Datastore entities. My GCF function sends a query with datastore.runQuery() which is common.
It took about a minute, but eventually all 32,000 entities were served by Express and loaded in the React app (browser) with no errors.
A basic Express route calls my GCF function:
app.post('/foo', (req, res) => {
myCloudFunction(req, res);
})
const myCloudFunction = (req, res) => {
// Inspects req, queries Datastore, and returns the results.
};
For this test, my React app just points to https://mydomain.example.com:3000/foo
(because my Express server listens on port 3000).
So it seems that GCF is not good enough for my application, unless I add pagination to the app (which is on the roadmap).
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