I have a composable in vue that uploads data to firebase storage. It works, but I cannot get the data out of the composable. I think it has something to do with a return or where I'm defining terms?
The code below is built around the documentation available for firebase Storage (https://firebase.google.com/docs/storage/web/upload-files).
useStorage.js
import { ref } from "vue";
import { projectStorage } from "../firebase/config";
import {
uploadBytesResumable,
getDownloadURL,
ref as storageRef,
} from "#firebase/storage";
const useStorage = () => {
const error = ref(null);
const url = ref(null);
const filePath = ref(null);
const uploadImage = async (file) => {
filePath.value = `images/${file.name}`;
const storageReference = storageRef(projectStorage,
filePath.value);
const uploadTask =
uploadBytesResumable(storageReference, file);
await uploadTask.on(
"state_changed",
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes)
* 100;
console.log("Upload is " + progress + "% done");
},
(err) => {
console.log(err);
error.value = err.message;
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL)
=> {
console.log("File available at", downloadURL);
url.value = downloadURL;
console.log(url.value); <--HAS CORRECT VALUE
return url.value; <--DOESNT DO ANYTHING
});
}
);
console.log(url.value);
};
return { url, filePath, error, uploadImage }; <--URL IS
NOT RETURNING OUT OF THIS COMPOSABLE
};
export default useStorage;
A simpeler approach would be to await getDownloadUrl: url.value = await getDownloadURL(uploadTask.snapshot.ref). Then, you can get rid of the .then on that function. Right now, return url.value is assigned to nothing.
Warning: Also write some sort of catch - in case something would go wrong - in a production environment.
Related
I need to implement this async function,
const uploadImage = async () => {
const filename = new Date().getTime() + photo!.name
const storage = getStorage(app)
const storageRef = ref(storage, filename)
const uploadTask = uploadBytesResumable(storageRef, photo!);
uploadTask.on('state_changed',
(snapshot) => {},
(error) => {
console.log("error while uploading photo", error)
},
async () => {
photoUrl = await getDownloadURL(uploadTask.snapshot.ref);
console.log("getDownloadURL", photoUrl)
return photoUrl
}
);
}
It is the function to upload images to Firebase-Storage. Here I need to return the "photoUrl ". I need to call the function like,
const res = await uploadImage(photo)
how do I do this? The uploaded image's URL should return from the function.
The object returned by uploadBytesResumable is also a promise, so you can just await that and then call getDownloadURL:
const uploadImage = async () => {
const filename = new Date().getTime() + photo!.name
const storage = getStorage(app)
const storageRef = ref(storage, filename)
const uploadTask = uploadBytesResumable(storageRef, photo!);
await uploadTask;
photoUrl = await getDownloadURL(uploadTask.snapshot.ref);
return photoUrl
}
You actually don't even need a reference to the task, as you already have the storageRef, the above can be shorted to:
const uploadImage = async () => {
const filename = new Date().getTime() + photo!.name
const storage = getStorage(app)
const storageRef = ref(storage, filename)
await uploadBytesResumable(storageRef, photo!);
return await getDownloadURL(storageRef);
}
Here is same thing to upload multiple files to firebase and return their URLs
async function uploadMultipleFilesToFirebase(imagesArray) {
try {
const requests = imagesArray.map(async (imageFile) => {
const storageRef = ref(storage, filename)
await uploadBytesResumable(storageRef, imageFile);
return await getDownloadURL(storageRef);
})
return Promise.all(requests)
} catch (error) {
throw({ error })
}
}
And then use it with:
urlsOfUploadedImages.value = await uploadProductToFirebase(productData)
I'm having a problem where the Array is not filled out, I think its something to do with the promoses resolving.
const UploadFile = async ({
imageName = `${Date.now()}`,
imageUris,
imageFolder = '',
metadata,
}: IFile) => {
if (imageUris) {
const promises: any[] = [];
const imageURLs: string[] = [];
imageUris.forEach(async (uri) => {
const randomNumber = Randomize('0', 10);
const finalImageName = `${Slugify(imageName)}`.toLowerCase();
const imagePath = `${imageFolder}/${finalImageName}-${randomNumber}`;
const imageRef = storageRef.child(imagePath);
const blob = (await fetch(uri)).blob();
const uploadTask = imageRef.put(await blob, metadata);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
},
(error) => console.log('Error:', error),
() => {
uploadTask.snapshot.ref.getDownloadURL().then((downloadURL) => {
console.log('File available at', downloadURL);
imageURLs.push(downloadURL);
});
},
);
promises.push(uploadTask);
});
// Not sure promise is resolving
Promise.all(promises).then((i) => {
console.log('All files uploaded', i);
});
Promise.all(imageURLs).then((i) => {
console.log('All imageURLs', i);
});
}
}
Output:
Retrieved listings
All files uploaded Array []
All imageURLs Array []
imageURLs Contents undefined
Upload is 0% done
Upload is 0% done
Upload is 100% done
File available at https://firebasestorage.googleapis.com/v0/b/wrecknet-ab69d.appspot.com/o/listings%2Fcar-5701393331?alt=media&token=ccfda911-36fb-4305-b6d7-0ee06fc824e1
Listing was successfully created
Upload is 100% done
File available at https://firebasestorage.googleapis.com/v0/b/wrecknet-ab69d.appspot.com/o/listings%2Fcar-4491812919?alt=media&token=03f72706-4201-4652-9172-8bcefaeb3e1f
As you can see the "All files uploaded Array []" and "All imageURLs Array []" arrays are empty, I suspect the Promise is not resolving.
As far as I know you can either listen to the on() of the UploadTask or to its then(), but not to both. Luckily you don't do anything meaningful in the on handling, so the entire code can be simplified down to:
const UploadFile = async ({
imageName = `${Date.now()}`,
imageUris,
imageFolder = '',
metadata,
}: IFile) => {
if (imageUris) {
const promises: any[] = [];
imageUris.forEach(async (uri) => {
const randomNumber = Randomize('0', 10);
const finalImageName = `${Slugify(imageName)}`.toLowerCase();
const imagePath = `${imageFolder}/${finalImageName}-${randomNumber}`;
const imageRef = storageRef.child(imagePath);
const blob = (await fetch(uri)).blob();
promises.push(imageRef.put(await blob, metadata));
});
Promise.all(promises).then((imageURLs) => {
console.log('All imageURLs', imageURLs);
});
}
}
i'm trying to upload and image from a device to firebase storage but i don't know which format i should use. i've try with put and putString, but both of them gave me invalid argument.
This is the code to pick and upload the image.
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.cancelled) {
setImage(result.uri);
}
};
const uploadImage = async () => {
if (!image) {
Alert.alert(
'You have to choose an image first'
);
} else {
const uri = image;
console.log(uri);
const filename = uri.substring(uri.lastIndexOf('/') + 1);
const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
setUploading(true);
setTransferred(0);
const task = firebase.storage()
.ref(filename)
.put(uploadUri);
// set progress state
task.on('state_changed', snapshot => {
setTransferred(
Math.round(snapshot.bytesTransferred / snapshot.totalBytes) * 10000
);
});
try {
await task;
} catch (e) {
console.error(e);
}
setUploading(false);
Alert.alert(
'Photo uploaded!',
'Your photo has been uploaded to Firebase Cloud Storage!'
);
setImage(null);
}
};
This is the uri of the image (the console.log output) :
This is the error using .put(uploadUri):
This is the error using .putString(uploadUri, 'data_url') :
In order to upload an image on Firebase storage using put you need to pass a blob as param instead of string.
For example you can do something like this:
import path from 'path'
const uid = 'image-id'
const fileName = uid + path.extname(uri)
const response = await fetch(uri)
const blob = await response.blob()
const uploadImage = firebase
.storage()
.ref()
.put(blob, {
contentType: `image/${path.extname(uri).split('.').pop()}`
})
uploadImage.on(
'state_changed',
snapshot => {
// progress
},
err => {
// error
},
() => {
// complete
}
I have a database listener in my code and I am trying to get every user's new posts and then (when I have all of them in an array) update the posts state.
My code looks like this but it is not working good, because setPosts is async and sometimes it might be called again before ending the state update. I think that I need to wrap the listener in a Promise but I have no idea how to do it detaching the listener when the component unmounts.
useEffect(() => {
const { firebase } = props;
// Realtime database listener
const unsuscribe = firebase
.getDatabase()
.collection("posts")
.doc(firebase.getCurrentUser().uid)
.collection("userPosts")
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach(async (change) => {
if (change.type === "added") {
// Get the new post
const newPost = change.doc.data();
// TODO - Move to flatlist On end reached
const uri = await firebase
.getStorage()
.ref(`photos/${newPost.id}`)
.getDownloadURL();
// TODO - Add the new post *(sorted by time)* to the posts list
setPosts([{ ...newPost, uri }, ...posts]);
}
});
});
/* Pd: At the first time, this function will get all the user's posts */
return () => {
// Detach the listening agent
unsuscribe();
};
}, []);
Any ideas?
Also, I have think to do:
useEffect(() => {
const { firebase } = props;
let postsArray = [];
// Realtime database listener
const unsuscribe = firebase
.getDatabase()
.collection("posts")
.doc(firebase.getCurrentUser().uid)
.collection("userPosts")
.orderBy("time") // Sorted by date
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach(async (change) => {
if (change.type === "added") {
// Get the new post
const newPost = change.doc.data();
// Add the new post to the posts list
postsArray.push(newPost);
}
});
setPosts(postsArray.reverse());
});
But in this case, the post uri is saved too in the firestore document (something I can do because I write on the firestore with a cloud function that gets the post from storage), and I don't know if it is a good practice.
Thanks.
Update
Cloud Function code:
exports.validateImageDimensions = functions
.region("us-central1")
.runWith({ memory: "2GB", timeoutSeconds: 120 })
.https.onCall(async (data, context) => {
// Libraries
const admin = require("firebase-admin");
const sizeOf = require("image-size");
const url = require("url");
const https = require("https");
const sharp = require("sharp");
const path = require("path");
const os = require("os");
const fs = require("fs");
// Lazy initialization of the Admin SDK
if (!is_validateImageDimensions_initialized) {
const serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
// ...
});
is_validateImageDimensions_initialized = true;
}
// Create Storage
const storage = admin.storage();
// Create Firestore
const firestore = admin.firestore();
// Get the image's owner
const owner = context.auth.token.uid;
// Get the image's info
const { id, description, location, tags } = data;
// Photos's bucket
const bucket = storage.bucket("bucket-name");
// File Path
const filePath = `photos/${id}`;
// Get the file
const file = getFile(filePath);
// Check if the file is a jpeg image
const metadata = await file.getMetadata();
const isJpgImage = metadata[0].contentType === "image/jpeg";
// Get the file's url
const fileUrl = await getUrl(file);
// Get the photo dimensions using the `image-size` library
getImageFromUrl(fileUrl)
.then(async (image) => {
// Check if the image has valid dimensions
let dimensions = sizeOf(image);
// Create the associated Firestore's document to the valid images
if (isJpgImage && hasValidDimensions(dimensions)) {
// Create a thumbnail for the uploaded image
const thumbnailPath = await generateThumbnail(filePath);
// Get the thumbnail
const thumbnail = getFile(thumbnailPath);
// Get the thumbnail's url
const thumbnailUrl = await getUrl(thumbnail);
try {
await firestore
.collection("posts")
.doc(owner)
.collection("userPosts")
.add({
id,
uri: fileUrl,
thumbnailUri: thumbnailUrl, // Useful for progress images
description,
location,
tags,
date: admin.firestore.FieldValue.serverTimestamp(),
likes: [], // At the first time, when a post is created, zero users has liked it
comments: [], // Also, there aren't any comments
width: dimensions.width,
height: dimensions.height,
});
// TODO: Analytics posts counter
} catch (err) {
console.error(
`Error creating the document in 'posts/{owner}/userPosts/' where 'id === ${id}': ${err}`
);
}
} else {
// Remove the files that are not jpeg images, or whose dimensions are not valid
try {
await file.delete();
console.log(
`The image '${id}' has been deleted because it has invalid dimensions.
This may be an attempt to break the security of the app made by the user '${owner}'`
);
} catch (err) {
console.error(`Error deleting invalid file '${id}': ${err}`);
}
}
})
.catch((e) => {
console.log(e);
});
/* ---------------- AUXILIAR FUNCTIONS ---------------- */
function getFile(filePath) {
/* Get a file from the storage bucket */
return bucket.file(filePath);
}
async function getUrl(file) {
/* Get the public url of a file */
const signedUrls = await file.getSignedUrl({
action: "read",
expires: "01-01-2100",
});
// signedUrls[0] contains the file's public URL
return signedUrls[0];
}
function getImageFromUrl(uri) {
return new Promise((resolve, reject) => {
const options = url.parse(uri); // Automatically converted to an ordinary options object.
const request = https.request(options, (response) => {
if (response.statusCode < 200 || response.statusCode >= 300) {
return reject(new Error("statusCode=" + response.statusCode));
}
let chunks = [];
response.on("data", (chunk) => {
chunks.push(chunk);
});
response.on("end", () => {
try {
chunks = Buffer.concat(chunks);
} catch (e) {
reject(e);
}
resolve(chunks);
});
});
request.on("error", (e) => {
reject(e.message);
});
// Send the request
request.end();
});
}
function hasValidDimensions(dimensions) {
// Posts' valid dimensions
const validDimensions = [
{
width: 1080,
height: 1080,
},
{
width: 1080,
height: 1350,
},
{
width: 1080,
height: 750,
},
];
return (
validDimensions.find(
({ width, height }) =>
width === dimensions.width && height === dimensions.height
) !== undefined
);
}
async function generateThumbnail(filePath) {
/* Generate thumbnail for the progressive images */
// Download file from bucket
const fileName = filePath.split("/").pop();
const tempFilePath = path.join(os.tmpdir(), fileName);
const thumbnailPath = await bucket
.file(filePath)
.download({
destination: tempFilePath,
})
.then(() => {
// Generate a thumbnail using Sharp
const size = 50;
const newFileName = `${fileName}_${size}_thumb.jpg`;
const newFilePath = `thumbnails/${newFileName}`;
const newFileTemp = path.join(os.tmpdir(), newFileName);
sharp(tempFilePath)
.resize(size, null)
.toFile(newFileTemp, async (_err, info) => {
// Uploading the thumbnail.
await bucket.upload(newFileTemp, {
destination: newFilePath,
});
// Once the thumbnail has been uploaded delete the temporal file to free up disk space.
fs.unlinkSync(tempFilePath);
});
// Return the thumbnail's path
return newFilePath;
});
return thumbnailPath;
}
});
I am trying to upload a single image to Firebase Storage, then grab its download url and assign this to a variable.
I can upload my image to firebase successfully, however I cannot retrieve the download url. here is what I have tried already.
upload() {
let storageRef = firebase.storage().ref();
let success = false;
for (let selectedFile of [(<HTMLInputElement>document.getElementById('file')).files[0]]) {
let router = this.router;
let af = this.af;
let folder = this.folder;
let path = `/${this.folder}/${selectedFile.name}`;
var iRef = storageRef.child(path);
iRef.put(selectedFile).then((snapshot) => {
console.log('Uploaded a blob or file! Now storing the reference at', `/${this.folder}/images/`);
af.list(`/${folder}/images/`).push({ path: path, filename: selectedFile.name })
});
}
// This part does not work
iRef.getDownloadURL().then((url) => {this.image = url});
console.log('IREF IS ' + iRef)
console.log('IMAGEURL IS ' + this.image)
}
The Console logs are these:
IREF IS gs://my-app-159520.appspot.com/images/Screen Shot 2017-08-14 at 12.19.01.png
view-order.component.ts:134 IMAGEURL IS undefined
Uploaded a blob or file! Now storing the reference at /images/images/
I have been trying to use the iRef reference to grab the download url but I keep getting errors. I am trying to grab the url so I can assign it to the this.image variable and then store it in my database using another function.
The API has changed. Use the following to get downloadURL
snapshot.ref.getDownloadURL().then(function(downloadURL) {
console.log("File available at", downloadURL);
});
I think I have figured this out and it seems to be working, I realised I had to grab the downloadURL from the snapshot and assign that to this.image like so:
upload() {
let storageRef = firebase.storage().ref();
let success = false;
for (let selectedFile of [(<HTMLInputElement>document.getElementById('file')).files[0]]) {
let router = this.router;
let af = this.af;
let folder = this.folder;
let path = `/${this.folder}/${selectedFile.name}`;
var iRef = storageRef.child(path);
iRef.put(selectedFile).then((snapshot) => {
// added this part which as grabbed the download url from the pushed snapshot
this.image = snapshot.downloadURL;
console.log('Uploaded a blob or file! Now storing the reference at', `/${this.folder}/images/`);
af.list(`/${folder}/images/`).push({ path: path, filename: selectedFile.name })
console.log('DOWNLOAD URL IS ' + this.image)
});
}
}
I then ran my other function to add the URL to the database and it has gone in ok where expected!
So I have uploaded the image to the database, then using the snapshot from the put function, I then assigned my variable image:any to to the snapshot downloadURL like so:
this.image = snapshot.downloadURL;
I hope this can help someone else!
.put() function is returning a task, which can be used to track the uploading state.
For example you can listen for progress, error or completion like so:
onUploadImage () {
const self = this
const file = self.selectedFile
if (!file) {
return
}
self.isUploading = true
const storageRef = firebase.storage().ref('/images/' + file.name)
const task = storageRef.put(file)
task.on('state_changed',
function progress (snapshot) {
self.status = 'UPLOADING...'
self.percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
},
function error () {
self.status = 'FAILED TRY AGAIN!'
self.isUploading = false
},
function complete (event) {
self.status = 'UPLOAD COMPLETED'
self.isUploading = false
storageRef.getDownloadURL().then((url) => { console.log(url) })
}
)
}
In 2019, I gained access to the url of a newly-saved file in firebase with the following function:
const uploadImage = async(uri) => {
const response = await fetch(uri);
const blob = await response.blob();
// child arg specifies the name of the image in firebase
const ref = firebase.storage().ref().child(guid());
ref.put(blob).then(snapshot => {
snapshot.ref.getDownloadURL().then(url => {
console.log(' * new url', url)
})
})
}
As of web version v9 (modular) you can achieve this in this way:
import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";
uploadImage = () => {
const storage = getStorage()
const reference = ref(storage, 'file_name.jpg')
const file = e.target.files[0]
uploadBytes(reference, file)
.then(snapshot => {
return getDownloadURL(snapshot.ref)
})
.then(downloadURL => {
console.log('Download URL', downloadURL)
})
}