I tried to download the image which is in firebase storage which link is store in database. When I tried to download the image, it takes more time to execute while for loop is completed.
Is there any process that somehow I download in time which doesn't make the function really slow? I already solve this issue using setTimeout but I hope there may be a better solution than mine. Help me! thank you!
export const shampooHandler = () => {
return (dispatch) => {
dispatch(shampooStart());
const data = [];
const imgList = [];
fire
.database()
.ref()
.child("Shampoo")
.once("value")
.then((response) => {
for (let i = 0; i < response.val().length; i++) {
fire.storage().refFromURL(response.val()[i].img).getDownloadURL().then((image) => {
imgList.push(image);
})
.catch((error) => {
dispatch(shampooError(error));
});
setTimeout(() => {
name = response.val()[i].name;
description = response.val()[i].description;
value = response.val()[i].value;
img = imgList[i];
data.push({ name, description, value, img });
if (i === (response.val().length - 1)) {
dispatch(shampooSuccess(data));
}
}, 3000);
}
})
.catch((error) => {
dispatch(shampooError(error));
});
};
};
I spend a day finding a right solution for it. It may help someone to find solution in future. Thanks guys for giving a thought and specially DougStevensen to tiggering me an idea
export const shampooHandler = () => {
return (dispatch) => {
dispatch(shampooStart());
const data = [];
const imglist = [];
fire.database().ref().child("Shampoo").once("value").then((response) => {
response.val().forEach(element => {
const promise = imageUrlHandler(element.img).then(url => {
return url;
}).catch(error =>{
dispatch(shampooError(error));
})
imglist.push(promise);
//all the promise call to download the images
Promise.all(imglist).then(items =>{
const dataCollection = {
name: element.name,
description: element.description,
value: element.value,
img: items[items.length - 1]
}
data.push(dataCollection);
if(data.length === response.val().length){
dispatch(shampooSuccess(data));
}
}).catch(err =>dispatch(shampooError(err)));
})
}).catch(error => {
dispatch(shampooError(error));
})
}
}
export const imageUrlHandler = (databaseUrl) => {
return new Promise((resolve,reject)=> {
fire.storage().refFromURL(databaseUrl).getDownloadURL().then((url) => {
resolve(url);
})
.catch((error) => {
reject(error)
});
})
}
Related
I need some help with me current project making in React. I'am making a star-wars-app for my job interview and I stucked on a one problem.
Fetch efficiency.
I'am fetching this data, and then fetching some more because of the url's in the first fetched data, and everything fetches good, but first i have the 'url's' seeing in the table and then it changes into correct data.
I want to set the 'fetched' state to true when everything is rendered correctly but I don't know how to do it.
const api = `https://swapi.dev/api/people/`;
const [characters, setCharacters] = useState([]);
const [speciesOptions, setSpeciesOptions] = useState([]);
const [homeworldOptions, setHomeworldOptions] = useState([]);
const [fetched, setFetched] = useState(false);
useEffect(() => {
const fetchedTimeout = () => {
setTimeout(() => {
setFetched(true);
}, 2000);
};
const fetchArray = (array, arrName) => {
for (let elem of array) {
fetch(elem).then((response) =>
response.json().then((data) => {
array.shift();
array.push(data.name);
})
);
}
if (arrName === "species") {
if (!array.length) {
array.push("Unspecified");
}
}
};
async function fetchOtherData(characters) {
await characters.forEach((character) => {
const homeworld = character.homeworld;
const vehicles = character.vehicles;
const starships = character.starships;
const species = character.species;
fetch(homeworld).then((response) =>
response.json().then((data) =>
setCharacters((prevData) =>
prevData.map((prevCharacter) =>
prevCharacter.homeworld === homeworld
? {
...prevCharacter,
homeworld: data.name,
}
: prevCharacter
)
)
)
);
fetchArray(vehicles);
fetchArray(starships);
fetchArray(species, "species");
});
await setCharacters(characters);
await fetchedTimeout();
}
const fetchAllCharacters = (allCharacters, data) => {
if (data.next) {
fetch(data.next)
.then((response) => response.json())
.then((data) => {
allCharacters.push(...data.results);
fetchAllCharacters(allCharacters, data);
});
}
if (!data.next) {
fetchOtherData(allCharacters);
}
};
async function fetchApi() {
const allCharacters = [];
await fetch(api)
.then((response) => response.json())
.then((data) => {
allCharacters.push(...data.results);
fetchAllCharacters(allCharacters, data);
})
.catch((error) => console.log(error));
}
const setSpeciesFiltering = () => {
const speciesFiltering = [];
for (let character of characters) {
const characterSpecies = character.species.join();
const foundSpecies = speciesFiltering.indexOf(characterSpecies);
if (foundSpecies === -1) {
speciesFiltering.push(characterSpecies);
}
}
const speciesOptions = speciesFiltering.map((species) => (
<option value={species}>{species}</option>
));
setSpeciesOptions(speciesOptions);
};
const setHomeworldFiltering = () => {
const homeworldFiltering = [];
for (let character of characters) {
const characterHomeworld = character.homeworld;
const foundSpecies =
homeworldFiltering.indexOf(characterHomeworld);
if (foundSpecies === -1) {
homeworldFiltering.push(characterHomeworld);
}
}
const homeworldOptions = homeworldFiltering.map((homeworld) => (
<option value={homeworld}>{homeworld}</option>
));
setHomeworldOptions(homeworldOptions);
};
fetchApi();
setSpeciesFiltering();
setHomeworldFiltering();
}, []);
I will appreciate your response.
Okay, after all the comments (thanks for that!) i changed the code to something like this.
useEffect(() => {
const fetchOtherData = (characters) => {
const charactersWithAllData = [];
characters.forEach((character) => {
const homeworld = character.homeworld;
const species = character.species;
const vehicles = character.vehicles;
const starships = character.starships;
let urls = [homeworld, ...species, ...vehicles, ...starships];
Promise.all(
urls.map((url) => {
if (url.length) {
fetch(url)
.then((response) => response.json())
.then((data) => {
if (url.search("species") > 0) {
character.species = data.name;
}
if (url.search("planets") > 0) {
character.homeworld = data.name;
}
if (url.search("vehicles") > 0) {
character.vehicles.shift();
character.vehicles.push(data.name);
}
if (url.search("starships") > 0) {
character.starships.shift();
character.starships.push(data.name);
}
})
.catch((err) => console.error(err));
}
if (!url.length) {
if (url.search("species")) {
character.species = "Unspecified";
}
if (url.search("vehicles")) {
character.vehicles = "";
}
if (url.search("starships")) {
character.starships = "";
}
}
})
).then(charactersWithAllData.push(character));
});
return charactersWithAllData;
};
const fetchApi = () => {
const characters = [];
Promise.all(
[api].map((api) =>
fetch(api)
.then((response) => response.json())
.then((data) => characters.push(...data.results))
.then((data) => {
setCharacters(fetchOtherData(characters));
})
)
);
};
fetchApi();
}, []);
In what point do i have to set the 'characters' state ? Because in the situation above the data first shows on the screen, and then the state is set.
As other comments say, using Promise.all and refactoroing your useEffect is best solution for this.
But this might be helpful if you don't want to change a lot.
(but still consider refactor your hook)
const [loading, setLoading] = useState(0);
const isLoading = loading > 0;
// replace your fetches with below:
const myFetch = async (path) => {
try {
setLoading(loading => loading + 1);
return await fetch(path);
} finally {
setLoading(loading => loading - 1);
}
};
useEffect(() => {
// do your stuffs
}, []);
I've been building an app with Firebase & React Native primarily using Firestore. I started to use Firestore and its been great, but for some reason when writing to Firestore, it is only working on the first attempt (when i remove the app, rebuild, and perform my write).
I tried to do the exact same thing except write to Firestore and everything works as expected.
I am also receiving no error!
Here is what I am doing:
export const addBrandProduct = (postObj) => {
return () => {
firebase
.firestore()
.collection('brandProducts')
.add(postObj)
.then((docRef) => {
console.log("Document written with ID: ", docRef.id);
Actions.categories();
})
.catch(error => {
console.error("Error adding document: ", error);
});
};
};
For more of a reference, here is my component code that calls addBrandProduct()
onUploadImages = () => {
let photo =
Platform.OS === 'ios'
? this.state.images.map(img => img.uri.replace('file://', ''))
: this.state.images.map(img => img.uri);
photo.forEach((image, i) => {
const sessionId = new Date().getTime();
const Blob = RNFetchBlob.polyfill.Blob;
const fs = RNFetchBlob.fs;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
window.Blob = Blob;
let uploadBlob = null;
let mime = 'image/jpg';
const imageRef = firebase
.storage()
.ref('brandProducts/')
.child(`${this.props.userData.uid}`)
.child(`${sessionId}-${i}`);
fs.readFile(image, 'base64')
.then(data => {
return Blob.build(data, {type: `${mime};BASE64`});
})
.then(blob => {
uploadBlob = blob;
return imageRef.put(blob, {contentType: mime});
})
.then(() => {
uploadBlob.close();
return imageRef.getDownloadURL();
})
.then(url => {
//if this is the last uploaded image, post data to db
if (i === this.state.images.length - 1) {
const urls = {
...this.state.urls,
[i]: url,
};
const postObj = {
...this.state.postObj,
urls: urls,
};
this.props.addBrandProduct(postObj);
} else {
this.setState({
urls: {
...this.state.urls,
[i]: url,
},
});
}
})
.catch(error => {
console.log(error);
});
});
};
Basically, I am uploading a maximum of 3 images along with some data for it. In order to ensure I am uploading them all prior to adding the post data (writing to firestore) I am using a forEach and on the last upload, when it completes, I am calling the action to write the post data.
Edition
Hum addBrandProduct is a function that create another function.
So when you call this.props.addBrandProduct(postObj) nothing is sent to firestore, you just create a new function that should be called.
Maybe you can go out this stuff and call firebase directly, ensuring that everything works and then go back to the redux way if you still want to use it. I also make it parallelized instead of sequentials. Hope it help, hard to find the real problem when it can come from anywhere.
onUploadImages = () => {
let photo = Platform.OS === 'ios'
? this.state.images.map(img => img.uri.replace('file://', ''))
: this.state.images.map(img => img.uri);
Promise.all( photo.map( image => {
const sessionId = new Date().getTime();
const Blob = RNFetchBlob.polyfill.Blob;
//This is kind useless
//const fs = RNFetchBlob.fs;
//This is not used
//window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
//This is not adviced
//window.Blob = Blob;
let uploadBlob = null;
let mime = 'image/jpg';
const imageRef = firebase
.storage()
.ref('brandProducts/')
.child(`${this.props.userData.uid}`)
.child(`${sessionId}-${i}`);
return fs.readFile(image, 'base64')
.then(data => {
return RNFetchBlob.polyfill.Blob.build(data, {type: `${mime};BASE64`});
})
.then(blob => {
uploadBlob = blob;
return imageRef.put(blob, {contentType: mime});
})
.then(() => {
uploadBlob.close();
return imageRef.getDownloadURL();
});
))
.then( results => {
//results is, here, [ urlFromFirst, urlFronSecond, ...]
const urls = { ...this.state.urls};
results.forEach( (r, i) => urls[i] = r );
const postObj = {
...this.state.postObj,
urls
};
return firebase
.firestore()
.collection('brandProducts')
.add(postObj)
})
.then( docRef => {
console.log("Document written with ID: ", docRef.id);
})
.catch(error => {
console.error(error);
});
};
I have this post screen where I can pick an image from the camera roll and type a text and I want it to be saved in Firebase.
Here is my code in fire.js
addPost = async({text,localUri}) => {
const remoteUri = await this.uploadPhotoAsync(localUri)
return new Promise((res,rej) => {
this.firestore.collection("posts").add({
text,
uid: this.uid,
timestamp:this.timestamp,
image: remoteUri
})
.then(ref => {
res(ref)
})
.catch(error => {
rej(error)
})
})
}
uploadPhotoAsync = async uri => {
const path = `photos/${this.uid}/${Date.now()}.jpg`
return new Promise(async (res,rej) => {
const response = await fetch(uri)
const file = await response.blob()
let upload = firebase.storage().ref(path).put(file)
upload.on(firebase.storage.TaskEvent.STATE_CHANGED,snapshot => {},
err => {
rej(err)
},
async () => {
const url = await upload.snapshot.ref.getDownloadURL()
res(url)
}
)
})
}
And here is my postscreen.js screen where I'm getting the error can't find variable atob,
please suggest me a solution.
handlePost = () => {
Fire.shared.addPost({text:this.state.text.trim(),
localUri:this.state.image })
.then(ref => {
this.setState({text:"",image:undefined})
this.props.navigation.goBack()
}).catch(error => {
alert(error)
})
}
pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing:true,
aspect:[4,3]
})
if(!result.cancelled) {
this.setState({image: result.uri})
}
}
By the way, I can see the image is saved in Firestore storage but I can't see the text and photo in the Firestore database
This is a bug in some versions of firebase.
A workaround is to import base64 in the app.js and define it in case it's not defined.
import {decode, encode} from 'base-64'
if (!global.btoa) { global.btoa = encode }
if (!global.atob) { global.atob = decode }
I want to save a bunch of Images to Firebase storage and it's saved very well "as known image by image " in Firebase Storage, so after I saved it I want to get all the Uri and put it into Real-time DB as an Array-object like this
but I'm tried here in this code and also save one image just like this!
So how to handle these to Get all the images in the Storage and then put them into an array in DB?
// Open Gallery
pickMultiple = () => {
ImagePicker.openPicker({
multiple: true
})
.then(images => {
this.setState({
images: images.map(i => {
return {
uri: i.path,
width: i.width,
height: i.height,
mime: i.mime
};
})
});
})
.catch(e => console.log(e));
};
_SaveImagesToFirebase = () => {
const uid = firebase.auth().currentUser.uid; // Provider
const { images } = this.state;
const provider = firebase.database().ref(`providers/${uid}`);
images.map(image => {
let file = image.uri;
const path = "Img_" + Math.floor(Math.random() * 1500 + ".jpg");
const ref = firebase
.storage()
.ref(`provider/${uid}/ProviderGalary/${path}`);
let imagesArray = [];
ref
.put(file)
.then(() => {
ref
.getDownloadURL()
.then(
images => {
console.log(images);
imagesArray.push({
uri: images
});
},
error => console.log(error)
)
.then(() => {
provider
.update({
Images: imagesArray
})
.then(() => console.log("done with imgs"));
});
console.log("#inside", imagesArray);
})
.then(() => {
setTimeout(() => {
this.props.navigation.navigate("Home");
}, 2000);
});
console.log("#OUT", imagesArray);
});
};
UH My bad, I just define imagesArray inside map() it should be outside! like this,
_SaveImagesToFirebase = () => {
const uid = firebase.auth().currentUser.uid; // Provider
const { images } = this.state;
const provider = firebase.database().ref(`providers/${uid}`);
=> let imagesArray = [];
images.map(image => {
let file = image.uri;
const path = "Img_" + Math.floor(Math.random() * 1500 + ".jpg");
const ref = firebase
.storage()
.ref(`provider/${uid}/ProviderGalary/${path}`);
ref
.put(file)
.then(() => {
ref
.getDownloadURL()
.then(
images => {
console.log(images);
imagesArray.push({
uri: images
});
},
error => console.log(error)
)
.then(() => {
provider
.update({
Images: imagesArray
})
.then(() => console.log("done with imgs"));
});
})
.then(() => {
setTimeout(() => {
this.props.navigation.navigate("Home");
}, 2000);
});
});
};
In my app, users are able to add a loved one, like so:
addLovedOne(event) {
const {
lovedOne,
} = this.state;
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
db.addLovedOne(user.uid, lovedOne)
.then(() => {
this.setState({ lovedOne: '' });
this.refreshLovedOnes();
})
.catch(error => {
this.setState(byPropKey('error', error));
});
} else {
unsubscribe();
}
});
event.preventDefault();
}
Naturally, users are able to remove loved ones, like so:
removeLovedOne(event) {
const lovedOne = event.target.id;
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
const ref = firebase.database().ref(`users/${user.uid}/lovedOnes`);
const removeLovedOne = ref.orderByChild('lovedOne').equalTo(lovedOne);
removeLovedOne.on('value', (snapshot) => {
const lovedOneId = snapshot.node_.children_.root_.key;
db.removeLovedOne(user.uid, lovedOneId, lovedOne)
.then(() => {
this.refreshLovedOnes();
})
.catch(error => {
this.setState(byPropKey('error', error));
});
});
} else {
unsubscribe();
}
});
}
Here is what the queries look like:
export const addLovedOne = (userId, lovedOne) => (
db.ref(`users/${userId}/lovedOnes`).push({
lovedOne,
})
);
export const removeLovedOne = (userId, lovedOneKey) => (
db.ref(`users/${userId}/lovedOnes/${lovedOneKey}/lovedOne`).remove()
);
Here is what my schema looks like:
Any idea why I'm not able to add a loved one with the same name after removing it without refreshing the page?