How to wait for a value by using Await - flutter

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();
}

Related

Why is (list.length == otherList.length) showing false - Flutter

I have a List<File> imageFiles made up of files that a user selects and when they press submit, each of the files is uploaded to the collection in Firebase Storage. For each one, the downloadUrl is added to another list uploadedImages. I cannot upload the document to Firestore until all the downloadUrls are added to the uploadedImages list so I used an if statement if (imageFiles.length == uploadedImages.length) {<upload document>}. The problem is, in those lists are not the same length until the forEach block completes, and even then it doesn't return true. I confirmed that all the files are uploaded to Firebase Storage and printed the uploadedImages so I know they are eventually the same length but don't know how to make the if statement recognize that. Here is my current code:
List<String> uploadedUrls = [];
/// Uploads each file and adds its URL
imageFiles.forEach((file) async {
String fileName = '${UniqueKey()}_post.png';
TaskSnapshot uploadTask = await FirebaseStorage.instance
.ref()
.child('/${user.uid}/posts/$postId/$fileName')
.putFile(file);
String url = await uploadTask.ref.getDownloadURL();
setState(() {
uploadedUrls.add(url);
});
print(uploadedUrls);
});
/// Uploads post if all files and URLs are added *NOT WORKING*
if (imageFiles.length == uploadedUrls.length) {
Post post =
Post(postId, uploadedUrls, user.uid, profile, location, Timestamp.now());
try {
FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.collection('posts')
.doc(postId)
.set(post.toJson()).then((value) {
setState(() {
_isLoading = false;
});
});
} on FirebaseAuthException {
setState(() {
_isLoading = false;
});
}
}
Because you are running an asynchronous function in forEach,it won’t wait until the task is completed. So you can either move the if condition inside the forEach’s callback below the setState or use a parent function as wrapper for both them, and it should await the first call
imageFiles.forEach((file) async {
String fileName = '${UniqueKey()}_post.png';
TaskSnapshot uploadTask = await FirebaseStorage.instance
.ref()
.child('/${user.uid}/posts/$postId/$fileName')
.putFile(file);
String url = await uploadTask.ref.getDownloadURL();
setState(() {
uploadedUrls.add(url);
});
if (imageFiles.length == uploadedUrls.length) {
....
});
or extract the forEach loop into a async function
void addDownloafUrls() asyn {
imageFiles.forEach((file) async { ... });
}
// And you can await this function
addDownloafUrls().then(() {
if (imageFiles.length == uploadedUrls.length) {...}
})

Get multiple Image download URLs from Firebase Storage- I can successfully upload multiple images

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));
}));

How to upload a image url downloaded from firebase storage to firestore document for newly registered user?

I have made it till uploading the image to firebase storage and download the url.Here, I need to store the newly registered user information in an Firestore document. I am storing some details one of those is the image URL downloaded from firebase storage.
Now, I need to assign the downloaded Url to Firestore to access it in my dart pages.
uploadImage() async {
var random = Random(25);
final StorageReference fireref = FirebaseStorage.instance
.ref()
.child('profilepics/${random.nextInt(5000).toString()}.jpg');
StorageUploadTask task = fireref.putFile(profilepic);
StorageTaskSnapshot snapshottask = await task.onComplete;
String downloadUrl = await snapshottask.ref.getDownloadURL();
if (downloadUrl != null) {
userManagement.addProfilePic(downloadUrl.toString()).then((val) {
Navigator.of(context).pushReplacementNamed('/twelf');
});
}
}
The above is the code of getting the image url from firebase storage.And, If you can see I have called a method addProfilePic to add the downloaded URL to firestore. And the method is,
Future addProfilePic(picUrl) async {
//---
}
What Should i write in this method to upload url to firestore. I am not able to understand.
Additional code for understanding:
class UserManagement {
storeNewUser(user, context) {
Firestore.instance.collection('/userdetails').add({
'Email': user.email,
'uid': user.uid,
'displayName': user.fullname,
'photoUrl': user.photoUrl
}).then((value) {
Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed('/selectpic');
}).catchError((e) {
print(e);
});
}
And the below code is near registration the final code that upload data to firestore:
final String userId = await widget.auth
.createUserWithEmailAndPassword(_email, _password)
.then((signedInUser) async {
var userUpdateInfo = new UserUpdateInfo();
userUpdateInfo.displayName = _fullname;
userUpdateInfo.photoUrl = _imageurl;
//'https://cdn.mos.cms.futurecdn.net/QjuZKXnkLQgsYsL98uhL9X-1024-80.jpg';
final user = await FirebaseAuth.instance.currentUser();
user.updateProfile(userUpdateInfo).then((user) {
FirebaseAuth.instance
.currentUser()
.then(
(user) => {UserManagement().storeNewUser(user, context)})
.catchError((e) {
print(e);
});
}).catchError((e) {
print(e);
});
}).catchError((e) {
print(e);
});
print('Registered user: $userId');
}
Try this:
Future<String> uploadImage(profilepic) async {
StorageUploadTask uploadTask = storageRef.child("profilepics/${random.nextInt(5000).toString()}.jpg").putFile(profilepic);
StorageTaskSnapshot storageTaskSnapshot = await uploadTask.onComplete;
String downlaodUrl = await storageTaskSnapshot.ref.getDownloadURL();
return downlaodUrl;
}
// String imageUrl = await uploadImage(profilepic); You can store the value of imageUrl in firebase document.
Firestore.instance.collection('/userdetails').add({
// All fields you want to have in a document
'photoUrl': imageUrl; // use the image url variable
});
/// To update a currentUser document
Firestore.instance.collection('/userdetails')
.document(your_user_document_name_here)
.updateData({
// All fields you want to update in a document
'photoUrl': imageUrl; // use the image url variable
});

Async function doesn't await

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;
});
},

Flutter- call function after function

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) {
...
}