I'm trying to upload an image to firebase storage but when I called the function, await is not executed to get the url. What am I missing in this?
Looking at this other topic I've got that the problem may be the "then", but how can I set the code to await the url?
Async/Await/then in Dart/Flutter
Future < String > uploadImage(File imageFile) async {
String _imageUrl;
StorageReference ref =
FirebaseStorage.instance.ref().child(firebaseUser.uid.toString());
await(ref.putFile(imageFile).onComplete.then((val) {
val.ref.getDownloadURL().then((val) {
_imageUrl = val;
print(val);
print("urlupload");
});
}));
print(_imageUrl);
print("urlnoupload");
return _imageUrl;
}
Thanks!
you had async/await when you didn't need it because you were capturing value in the then function
Future<String> uploadImage(File imageFile) async {
String _imageUrl;
StorageReference ref = FirebaseStorage.instance.ref().child(firebaseUser.uid.toString());
return ref.putFile(imageFile).onComplete.then((val) {
return val.ref.getDownloadURL()
}).then((_imageUrl) {
return _imageUrl;
});
},
Related
I am trying to upload multiple (max 5) xfiles images (image_picker library) to firebase storage. currently, it uploads only one with the below method:
Future<void> addProduct({
BuildContext? ctx,
String? proName,
String? productDescription,
double? count,
double? price,
required List<XFile> images
}) async {
try {
var imageUrls = await Future.wait(images.map((_image) =>
uploadFile(_image)));
await firestore
.collection(outletsCollection)
.doc(_currentUserOutletId)
.set({
products: FieldValue.arrayUnion([
{
productId: Uuid().v4(),
productName: proName,
prodDesc: productDescription,
countInStock: count,
productPrice: price,
productImg: FieldValue.arrayUnion([imageUrls]),
}
]),
});
} catch (err) {
String errMsg = "error blahblah";
errorDialog(ctx!, errMsg);
}
notifyListeners();
}
for the uploadFile method i tried using images.forEach() instead of .map(), but it gave me void type error. uploadFile method:
Future<String> uploadFile(XFile _image) async {
var storageReference = storage
.ref()
.child('product_images')
.child(_currentUserOutletId!)
.child(Uuid().v4());
final metadata = SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {'picked-file-path': _image.path},
);
if (kIsWeb) {
await storageReference.putData(await _image.readAsBytes(), metadata);
} else {
await storageReference.putFile(io.File(_image.path), metadata);
}
return await storageReference.getDownloadURL();
}
so, at the end only one image is uploaded and addProduct() does not add product to firestore database. log in android studio returns nothing. I couldn't find a method that would work with the xfile. help appreciated very much!
List<XFile> _imageFileList;
try{
final pickedFile = await _picker.pickMultiImage();
setState(() {
_imageFileList = pickedFile;
_imageFileList.forEach((element) async {
firebase_storage.FirebaseStorage storage = firebase_storage.FirebaseStorage.instance;
var date = DateTime.now().millisecondsSinceEpoch;
try {
setState(() {
_loading = true;
});
firebase_storage.UploadTask task = firebase_storage.FirebaseStorage.instance.ref('uploads/photos/'+date.toString()+'_image.png').putData(await element.readAsBytes(), SettableMetadata(contentType: 'image/jpeg'));
await storage.ref('uploads/file-to-upload.png').putData(await element.readAsBytes(), SettableMetadata(contentType: 'image/jpeg'));
firebase_storage.TaskSnapshot downloadUrl = (await task);
String url = (await downloadUrl.ref.getDownloadURL());
// print(url.toString());
} on FirebaseException catch (e) {
// e.g, e.code == 'canceled'
setState(() {
_loading = false;
});
}
});
try this way using image_picker plugin. You can get url after image upload and you have yo it in your data.
I am trying to upload multiple images to firebase storage at once. I can upload successfully but cannot retrieve the download URLs;
Future<void> saveImages() async {
int index = images.length;
setState(() {
isLoading = true;
});
try {
final ref = FirebaseStorage.instance
.ref()
.child('images/${FirebaseAuth.instance.currentUser!.uid}');
images.forEach((element) async {
final file = await element.file;
final refPut = ref.child('$element$index');
await refPut.putFile(
File(file!.path),
);
});
final ref2 = ref.child('${imagePciker.path.split('/')}');
await ref2.putFile(File(imagePciker.path));
final ListResult listData = await ref.listAll();
final data =await ref2.getDownloadURL();
print('Im HERE!=Line 95');
Future.forEach(listData.items, (Reference element) async {
await element.getDownloadURL().then((value) => listUrls.add(value));
print('line 101');
print(listUrls.length);
});
print('Line 104');
await FirebaseFirestore.instance.collection('test').add({
'titleDownloadUrl': data,
'ListOfDownloadUrl': listUrls,
});
print('Line 108');
setState(() {
isLoading = false;
});
} catch (e) {
/* setState(() {
isLoading = false;
}); */
print(e);
}
}
The print statements are to debug. This whole function returns and no errors are thrown.
However in the firebase collection 'test' the URLs are not stored correctly.
On the first/fresh run after restart of the app.
the array of ListOfDownloadUrl is empty
On a hot restart(with the no images saved in firebase storage)
the array of ListOfDownloadUrl has one URL
then on multiple restarts, the amount of URLs saved becomes huge(ie. 4 images uploaded-10 URLs saved).
Please comment if I need to add anything else;
Thanks for any Help
.forEach() is NOT ASYNCHRONOUS - it WILL NOT wait. Use await Promise.all(images.map()) (thus creating an array of promises, which must all resolve).
Solved!
Following the thinking of #LeadDreamer
The forEach isn't ASYNCHRONOUS, the problem with his answer is that flutter does not have
Promise.all()
What it does have if Future.wait(), which acts(in my case) the same way.
Future<void> saveImages() async {
int index = images.length;
setState(() {
isLoading = true;
});
try {
final refMain = FirebaseStorage.instance
.ref()
.child('images${FirebaseAuth.instance.currentUser!.uid}');
AssetEntity eleId =
new AssetEntity(id: '', typeInt: 5, width: 5, height: 5);
await Future.wait(images.map((element) async { <-- here instead of the for each
setState(() {
eleId = element;
});
final file = await element.file;
final refPut = refMain.child('suportImage$_counter/$element$index');
await refPut.putFile(
File(file!.path),
);
}));
final ref2 =
refMain.child('headingImage$_counter/${imagePciker.path.split('/')}');
await ref2.putFile(File(imagePciker.path));
final ListResult listData =
await refMain.child('suportImage$_counter').listAll();
final data = await ref2.getDownloadURL();
await Future.wait(listData.items.map((e) async { <-- here instead of for each
await e.getDownloadURL().then((value) => listUrls.add(value));
}));
I'm trying to call a cloud firestore function that has to persist an object only when another function returns the url of the file in Firebase Storage but the async await is not working and the second function is called anyway whereas the first function is not yet completed!!!
await schoolProfileProvider.uploadSchoolProfileAvatar(data).then( (data) {
schoolProfileProvider.addSchoolProfile(data);
});
print('PROFILE ADDED');
Future<SchoolProfileData> uploadSchoolProfileAvatar(SchoolProfileData data) async {
List<File> avatars = [];
data.childrenDetails.forEach((child) {
avatars.add(File(child.childImage));
});
try {
await _api.uploadFilesToStorage(avatars, 'image', 'png').then((urls) {
for (var i = 0; i < urls.length; i++) {
data.childrenDetails[i].childImage = urls[i];
print('ADD ' + data.childrenDetails[i].childImage);
}
});
} on Exception catch (e) {
print(e.toString());
}
return data;
}
T cast<T>(x) => x is T ? x : null;
Future<List<String>> uploadFilesToStorage(List<File> files, String type, String extension) async {
final urls = <Future<String>>[];
files.forEach((file) async {
StorageReference storageRef = storage.ref().child(file.path);
final StorageTaskSnapshot downloadUrl =
(await storageRef
.putFile(file, StorageMetadata(contentType: type + '/' + extension))
.onComplete);
await downloadUrl.ref.getDownloadURL().then((url) {
urls.add(cast<Future<String>>(url));
print('URL for file ${file.path} = ${url.toString()}');
});
});
print ('urls returned');
return Future.wait(urls);
}
Future addSchoolProfile(SchoolProfileData data) async{
var result;
try {
result = await _api.addDocument(data.toJson());
} on Exception catch(e) {
print (e.toString());
}
return result;
}
I've managed to make the things work and execute addSchoolProfile only after uploadFilesToStorage is completed by reducing the nested functions and making the await downloadUrl.ref.getDownloadURL() as the last instruction returned in the callee function.
Please find the code for who is interested in :
The caller :
schoolProfileProvider.addSchoolProfileAndUploadAvatar(data);
Future addSchoolProfileAndUploadAvatar(SchoolProfileData data) async {
List<File> avatars = [];
data.childrenDetails.forEach((child) {
avatars.add(File(child.childImage));
});
try {
for (int i=0;i<avatars.length;i++){
await _api.uploadFileToStorageAndGetUrl(avatars[i], 'image', 'png').then((url) {
print('URL for file ${avatars[i].path} = ${url.toString()}');
data.childrenDetails[i].childImage = url;
print('ADD ' + data.childrenDetails[i].childImage);
});
}
_api.addDocument(data.toJson()) ; // Add document in Google Firebase after setting the avatar url
} on Exception catch (e) {
print(e.toString());
}
}
The callee :
Future <String> uploadFileToStorageAndGetUrl(File file,String type, String extension) async{
StorageReference storageRef = storage.ref().child(file.path);
final StorageTaskSnapshot downloadUrl =
(await storageRef
.putFile(file, StorageMetadata(contentType: type + '/' + extension))
.onComplete);
return await downloadUrl.ref.getDownloadURL(); // return the url of the file in Google Storage
}
I'm using Flutter to upload an image to Firebase and have a function submit() that is triggered when I submit the form. On submission, I validate that the submission is accurate and I call the uploadFile function to upload the designated image to Firebase storage and return the URL, which I set to urlForPost.
I want to wait for this urlForPost value to be set before triggering the remainder of the submit() function that uploads this to Firebase. Currently, it is returning a null value for urlForPost. How do I wait for the uploadFile() function to load so that I can prevent urlForPost from being null?
void submit() async {
// First validate form.
if (this._formKey.currentState.validate()) {
_formKey.currentState.save();// Save our form now.
final urlForPost = await uploadFile();
Firestore.instance
.collection('posts')
.document(documentName)
.collection('collection')
.add({
'user': widget.userPoster,
'post': _data.post,
'url': urlForPost,
'timePosted': Timestamp.now(),
});
Firestore.instance.collection('current_goals').document(widget.userPoster).collection(widget.goalType).document(widget.goalID).updateData(
{
'complete': true,
}
);
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => Home())); }
}
String downloadUrl;
Future<String> uploadFile() async {
final String rand1 = "${new Random().nextInt(10000)}";
final String rand2 = "${new Random().nextInt(10000)}";
final String rand3 = "${new Random().nextInt(10000)}";
final StorageReference ref = FirebaseStorage.instance.ref().child('${rand1}_${rand2}_${rand3}.jpg');
await ref.putFile(widget.selectedImage).onComplete.then((val) {
val.ref.getDownloadURL().then((val) {
print(val);
downloadUrl = val; //Val here is Already String
});
});
return downloadUrl;
}
You could change your uploadFile method to await for the upload as well.
You use await to make an asynchronous call synchronous. However, if you mix it with .then() methods, you're likely to make parts of it unintentionally asynchronous.
Future<String> uploadFile() async {
final String rand1 = "${new Random().nextInt(10000)}";
final String rand2 = "${new Random().nextInt(10000)}";
final String rand3 = "${new Random().nextInt(10000)}";
// you don't need {} if it's a simple statement, use $stringvar and ${value.toString}
final StorageReference ref = FirebaseStorage.instance.ref().child('$rand1_$rand2_$rand3.jpg');
StorageUploadTask task = ref.putFile(imageFile);
var downloadUrl = await (await task.onComplete).ref.getDownloadURL();
debugPrint("downloadUrl=$downloadUrl");
return downloadUrl.toString();
}
just a suggestion unrelated to your original question
Using 3 random numbers, you're likely to have a collision over time. Consider using the UUID package, the chances of collision are dramatically smaller. :)
Future<String> uploadFile() async {
final String uuid = uuid.v4(); // v4 is random, v1 is time-based
final StorageReference ref = FirebaseStorage.instance.ref().child('$uuid.jpg');
StorageUploadTask task = ref.putFile(imageFile);
var downloadUrl = await (await task.onComplete).ref.getDownloadURL();
return downloadUrl.toString();
}
I want to call function2 after function1 finished.
To do that I did like that.
this is function 1.
Future _uploadImages() async {
setState(() {isUploading = true;});
images.forEach((image) async {
await image.requestThumbnail(300, 300).then((_) async {
final int date = DateTime.now().millisecondsSinceEpoch;
final String storageId = '$date$uid';
final StorageReference ref =
FirebaseStorage.instance.ref().child('images').child(storageId);
final file = image.thumbData.buffer.asUint8List();
StorageUploadTask uploadTask = ref.putData(file);
Uri downloadUrl = (await uploadTask.future).downloadUrl;
final String url = downloadUrl.toString();
imageUrls.add(url);
});
});
}
this is function 2
Future _writeImageInfo() async {
await _uploadImages().then((_) async {
await Firestore.instance.collection('post').document(uid).setData({
'imageUrls': imageUrls,
}).then((_) {
Navigator.of(context).pop();
});
}
But console says function2's imageUrls called when list length = 0 because it's called before function 1 finished.
I don't know why that function not called after function 1.
How can I make this right?
This happens because of your images.forEach. The .forEach doesn't work with async callback. Therefore it doesn't wait the end of each the foreach to continue the function.
In general, don't use .forEach in dart anyway. Dart did a great job on the for keyword directly.
So ultimately, you should do the following:
for (final image in images) {
...
}