I am trying to upload multiple pictures to Cloud Storage through Firebase. I am using expo ImagePicker, which can take multiple images using the camera.
Here is code,
const [image, setImage] = useState([]);
const pickImageOnly = async () => {
let result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.All,
allowsEditing: true,
quality: 0.5,
});
if (!result.cancelled) {
setNextImg(true);
setImage((images) => images.concat(result.uri));
}
};
Now, I am trying to store those images in the firestore database. However, only one image is stored in the database, Here is my try.
const uploadImage = async () => {
for (let step = 0; step < image.length; step++) {
const uploadUri = image[step];
let filename = uploadUri.substring(uploadUri.lastIndexOf('/') + 1);
setUploading(true);
setTransferred(0);
const response = await fetch(uploadUri);
const blob = await response.blob();
const storageRef = firebase.storage().ref().child(`${user.email}/images/${filename}`)
const task = storageRef.put(blob);
// Set transferred state
task.on('state_changed', (taskSnapshot) => {
// console.log(
// `${taskSnapshot.bytesTransferred} transferred out of ${taskSnapshot.totalBytes}`,
// );
setTransferred(
Math.round(taskSnapshot.bytesTransferred / taskSnapshot.totalBytes) *
100,
)
})
try {
const ref = await task;
const url = await storageRef.getDownloadURL()
setUploading(false);
setImage(null);
return url;
} catch (e) {
console.log(e);
return null;
}
}
}
How can I store all the images in the database?
Related
I am hosting a react app in aws amplify using the aws-serverless version of express as the REST API, which sits inside of a lambda function. A big problem that I am facing is that asynchronous jobs in aws-serverless express cause the lambda function to complete before the promises resolve. Leaving me with no data and no error handling. This caused me to bring a lot of the asynchronous work to the front end of the application.
The problem here is that I need to bring a large amount of data into state. Right now, I am using a delay workaround (shown below) but instead need a programatic way to make sure state is finished updating before being used in the second useEffect hook (dependent on odds & failedTries props) instead of using the delay functionality.
Any help would be greatly appreciated.
const App = ({ signOut }) => {
const [odds, setOdds] = useState([]);
const [updateTime,setUpdateTime] = useState(0);
const [failedTries,setFailedTries] = useState(0);
useEffect(() => {
const setNflOdds = async () => {
let response = await updateNflOdds();
let data = response;
setOdds(data);
};
setNflOdds();
setUpdateTime(1);
const interval = setInterval(() => {
setNflOdds();
setUpdateTime(updateTime => updateTime +1);
}, 100000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
const s3Push = (() => {
if(!odds.length) {
setFailedTries(failedTries => failedTries + 1);
} else {
const delay = ms => new Promise(res => setTimeout(res, ms));
const nflOddsRefDelay = async() => {
*//This is the current workaround, wait ten seconds before pushing odds state up to the s3 bucket*
await delay(10000);
oddsS3Helper(odds);
};
nflOddsRefDelay()
}
});
s3Push();
}, [odds, failedTries]);
With the above indicated delay workaround this works for my use case (13k records inside of the array) but the data size is highly variable and I want to figure out a way that no matter the data size it brings the entire call up to the s3 bucket.
below is the content of the functions being called in the useEffect hook
const pushToS3 = async ( file, key ) => {
const creds = await Auth.currentCredentials()
const REGION = {region};
const s3Client = new S3Client({
credentials: Auth.essentialCredentials(creds),
region: REGION
});
const params = {
Bucket: {s3 bucket name}
Key: key,
Body: file,
};
s3Client.send(new PutObjectCommand(params));
console.log("file is sent");
};
const oddsS3Helper = (async (odds) => {
console.log("inside s3 helper: ",odds);
let csv = '';
let headers = Object.keys(odds[0]).join(',');
let values = odds.map(odd => Object.values(odd).join(',')).join('\n');
csv += headers + '\n' + values;
const buffedFile = csv;
const key = 'nflprops.csv'
const delay = ms => new Promise(res => setTimeout(res, ms));
const propRefDelay = async() => {
await delay(5000);
await postNflOdds();
};
pushToS3( buffedFile, key );
await propRefDelay();
});
async function getNflGames() {
const apiName = {name of serverless API inside of lambda};
const path = {path name};
const init = {
headers: {} // OPTIONAL
};
const data = await API.get(apiName, path, init);
return data;
};
async function getNflOdds(gameId) {
const apiName = {name of serverless API inside of lambda};
const path = {path name};
const init = {
headers: {}, // OPTIONAL
body: { gameId }
};
const data = await API.post(apiName, path, init);
return data;
};
async function updateNflOdds() {
const ojNflGames = await getNflGames();
const nflGameProps = [];
const nflOddsPush = ( async () => {
try {
await ojNflGames.data.map( async (game) => {
const ojNflOdds = await getNflOdds(game.id)
await ojNflOdds.data[0].odds.map((line) => {
nflGameProps.push(
{
gameId: game.id,
oddsId: line.id,
sports_book_name: line.sports_book_name,
name: line.name,
price: line.price,
checked_date: line.checked_date,
bet_points: line.bet_points,
is_main: line.is_main,
is_live: line.is_live,
market_name: line.market_name,
home_rotation_number: line.home_rotation_number,
away_rotation_number: line.away_rotation_number,
deep_link_url: line.deep_link_url,
player_id: line.player_id,
}
);
});
});
} catch (err) {
console.log("there was an error", err);
}
});
try {
await nflOddsPush();
} catch(err) {
console.log("odds push errored: ", err);
}
console.log("inside of updateNflOdds function: ",nflGameProps);
return nflGameProps;
};
I have reactjs app and some images to show. Now I try to optimise my images. So I tried to resize and change the image to Webp format using firebase's Resize Image extension. So I got the images with name, example test_album_40x40.webp, test_album_300x300.webp. My question is that how can I get that url. When I upload image.jpeg, I got the following url return from firestore.
test_album.jpeg (original url that return from firestore when it is upload and store it in db)
https://firebasestorage.googleapis.com/v0/b/myanmargita-bf003.appspot.com/o/images/albums/test_album.jpeg?alt=media&token=edd4799b-5b23-4940-b0f8-88ce57329625
test_album_40x40.webp
https://firebasestorage.googleapis.com/v0/b/project_id.appspot.com/o/images/albums/thumbs/test_album_40x40.webp?alt=media&token=cb2b2c92-68c6-49e8-8b64-b2788b7a1396
test_album_300x300.webp
https://firebasestorage.googleapis.com/v0/b/myanmargita-bf003.appspot.com/o/images/albums/thumbs/test_album_300x200.webp?alt=media&token=cb2b2c92-68c6-49e8-8b64-b2788b7a1396
Update Event to firestore and save to db
const handleSave = async (data) => {
const file = data.album_cover;
const storageRef = ref(storage, "images/albums/" + file.name);
const uploadTask = uploadBytesResumable(storageRef,file);
uploadTask.on(
"state_changed",
(snapshot) => {
var progress = Math.round(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
setUploadProgress({ progress });
},
(error) => {
throw error;
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
data.album_cover = url;
add_albumByArtist({
variables:addVariable(data, auth),
refetchQueries: [getAlbums]
});
}
);
}
);
navigate(-1);
};
I ran into the same issue, this is how I did it. I am doing it only for 100x100 size but you can call it multiple times according to your needs.
uploadStatus.on("state_changed",(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
setImageUploadProgress(progress);
},
(error) => {
throw error;
},
async () => {
const url = await getDownloadURL(uploadStatus.snapshot.ref);
const imagePath = uploadStatus.snapshot.ref.fullPath;
let imageName = imagePath.substring(imagePath.lastIndexOf("/") + 1);
const extension = imageName.split(".").pop();
const name = imageName.split(".").slice(0, -1).join(".");
imageName = name + "_" + "100x100" + "." + extension;
const storageRef = ref(firebaseStorage, "path_to_storage_resize_images_folder" + imageName);
const thumbURL = await getDownloadURL(storageRef);
setImageSource(url);
setImageThumb(thumbURL);
});
I'm trying to upload multiple images from react-native to firebase. The way I go about this is by using expo image picker multiple - by monstrodev ( see snack.io here )
I've managed to get it to work, and managed to be able to choose multiple photos from the 'improvised' camera roll. They load fine in the add screen, but I cannot find a solution on how to upload them properly.
export default function Add(props) {
const [name, setName] = useState("");
const [photos, setPhotos] = useState(null);
const uploadImage = async () => {
const uri = photos[0].uri; // -> uri is like this because 'media' is an array with objects inside that contain name, type, and uri each, and I only need uri of each oject.
const childPath = `data/${firebase.auth().currentUser.uid}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const upload = firebase
.storage()
.ref()
.child(childPath)
.put(blob);
const uploadProgress = snapshot => {
console.log(`transferred: ${snapshot.bytesTransferred}`)
};
const uploadComplete = () => {
upload.snapshot.ref.getDownloadURL().then((snapshot) =>{
addPost(snapshot);
console.log(snapshot);
})
};
const uploadError = snapshot => {
console.log(snapshot);
};
upload.on("state_changed", uploadProgress, uploadError, uploadComplete );
};
const addPost = (downloadURL) => {
firebase.firestore()
.collection("allPosts")
.collection(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURL,
name,
}).then((function () {
props.navigation.popToTop()
}))
}
useEffect(() => {
const {params} = props.route;
if(params) {
const {photos} = params;
if(photos) setPhotos(photos)
delete params.photos
}
}, [{photos}]) // This useEffect updates when coming back from the ImageBrowserScreen (can be found in snack.io, it's exactly the same)
The main problem is, let's say, I choose 3 photos. If I console.log(photos) I get this:
Array [
Object {
"name": "name1.JPG",
"type": "image/jpg",
"uri": "file:///name1.jpg",
},
Object {
"name": "name2.JPG",
"type": "image/jpg",
"uri": "file:///name2.jpg",
},
Object {
"name": "name3.JPG",
"type": "image/jpg",
"uri": "file:///name3.jpg",
},
]
The only I could get it to work was this, give exact path to uri (photos[0].uri for example) otherwise get network error. But this only uploads the first object/photo. I also tried to map through the photos state and return all uri's into a single array and use that as const uri, but that obviously didn't work, for uri needs only one string. So I somehow need to run that function for each uri to be able to get a downloadURL and store each of them.
EDIT:
const uploadImage = async (photo) => {
const uri = photo.uri;
const childPath = `data/${
firebase.auth().currentUser.uid
}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const snapshot = await firebase.storage().ref().child(childPath).put(blob);
const downloadURL = await snapshot.ref.getDownloadURL();
imgs.push(downloadURL)
};
const uploadPhotos = async () => {
await Promise.all(photos.map(p=>uploadImage(photo)).then(addPost())
};
Can you try it with a loop trough all photos and upload them separately. Idealy using a Promise.all to upload them in parallel:
const addPost = async (downloadURLs) => {
await firebase.firestore()
.collection("allPosts")
.collection(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURLs,
name,
})
props.navigation.popToTop()
}
const uploadImage = async (photo) => {
const uri = photo.uri;
const childPath = `data/${
firebase.auth().currentUser.uid
}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const snapshot = await firebase.storage().ref().child(childPath).put(blob);
const downloadURL = await snapshot.ref.getDownloadURL();
return downloadURL
};
const uploadPhotos = async () => {
const downloadURLs=await Promise.all(photos.map(p=>uploadImage(photo))
await addPost(downloadURLs);
};
const sendImageToFirebase = (e) => {
const promises = []
const urlsArray = []
// productimage is an array of image files
productImage.forEach((image, i) => {
var storageRef = firebase.storage().ref();
var uploadTask = storageRef.child(`${userDetailsFirebase.uid}/` + Math.random()).put(image);
promises.push(uploadTask.on('state_changed',
(snapshot) => {
},
(error) => {
console.log("error");
},
async () => {
const downloadurl = await uploadTask.snapshot.ref.getDownloadURL()
urlsArray.push(downloadurl)
}
))
})
Promise.all(promises).then(res => {
db.collection("products").doc(idGeneratedforProduct).set(
{
imageURL: urlsArray, //array of image urls
},
).then(e => {
}).catch(error => console.log("Error while sendig items to Firebase"))
})
}
I want to upload a multiple images to firebase storage. Here, sendImagToFirebase is a normal function in reactJs, and productimage is an array of image files. I want to wait for URL for each image files and then store all of them as an array to firestore. I would appreciate your input on how to do it?
You can create a function that receoves the ref and the file and returns the downloadURL. By calling it for each file with a Promise.all you get as result your array of downloadURLs:
const uploadFileAndGetDownloadURL = async (ref, file) => {
const snap = await ref.put(file);
const downloadURL = await snap.ref.getDownloadURL();
return downloadURL;
};
const sendImageToFirebase = async (e) => {
const promises = [];
productImage.forEach((image, i) => {
var storageRef = firebase.storage().ref();
var ref = storageRef.child(`${userDetailsFirebase.uid}/` + Math.random());
promises.push(uploadFileAndGetDownloadURL(ref, image));
});
//Your array with the urls
const urlsArray = await Promise.all(promises);
};
Been trying to upload an image to firebase storage but haven't had any luck thus far. So when I pick an image from the library it displays it but after I try on pressing the post button it just gives me an error.
Here's the error I get: https://imgur.com/a/KLSeu34
export default PostScreen = () => {
const [image, setImage] = useState(null);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
width: 300,
height: 400,
quality: 1,
});
if (!result.cancelled) {
console.log("user cancelled image");
setImage(result.uri);
}
};
const submitPost = async () => {
const result = await uploadImage();
console.log("Image Url: ", imageUrl);
};
const uploadImage = async (uri, imageName) => {
const response = await fetch(uri);
const blob = await response.blob();
var ref = firebase
.storage()
.ref()
.child("images/" + imageName);
return ref.put(blob);
};