Flutter- call function after function - flutter

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

Related

returning a String when getting error: type 'Future<dynamic>' is not a subtype of type 'String'

I can't work out how to return a string from a function in Dart (a Flutter app).
I am using SharedPreferences to capture input from the user. I have two functions, one to save preferences:
save(key, value) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
print('saved $value');
}
and one to read preferences:
read(key) async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getString(key) ?? 0;
print('$value');
}
This is working, but when I try to replace the print line with a return:
read(key) async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getString(key) ?? 0;
return('$value');
}
to return a string for the value, it throws an error:
type 'Future' is not a subtype of type 'String'
I have tried calling it many MANY different ways, but can't figure out what I assume is an incredibly basic problem. I noticed in some posts that this is a suggested solution, which works to print out the value, but I don't want to print it, i want it as a String variable:
read(mykey).then((value) => '$value');
I need to combine the value with other some other string values and make some minor manipulations (so printing it isn't helpful)
UPDATE
I have defined the function as #Stijn2210 suggested, but am still having problems getting the output i need.
Future<String> read(key) async {
final prefs = await SharedPreferences.getInstance();
final value = await prefs.getString(key) ?? '';
return value;
}
When I call this function from my app (this is a simplified snippet):
void onDragEnd(DraggableDetails details, User user) {
final minimumDrag = 100;
Future<String> myvalue;
if (details.offset.dx > minimumDrag) {
user.isSwipedOff = true;
save(user.imgUrl, 'Dog');
}
myvalue = read(user.imgUrl);
print(myvalue);
It's printing :
Instance of 'Future'
Whereas I want myvalue to be 'Dog'... Appreciate any insights!!
Really appreciate your answer #Stijn2202
Solution was to edit the method definition:
Future<void> onDragEnd(DraggableDetails details, User user) async
and then call the read function from the method with this:
final String myvalue = await read(user.imgUrl);
getString is a Future, which you can handle by using await or as you are doing, using then
However, in my opinion using await is your better option. This would look like this:
Future<String> getMyString() async {
final prefs = await SharedPreferences.getInstance();
final value = await prefs.getString(key) ?? '';
// Don't use 0, since it isnt an int what you want to return
return value;
}
EDIT:
based on your code snippet, this is how you should call your read method:
Future<void> onDragEnd(DraggableDetails details, User user) async {
final minimumDrag = 100;
if (details.offset.dx > minimumDrag) {
user.isSwipedOff = true;
save(user.imgUrl, 'Dog');
}
final String myvalue = await read(user.imgUrl);
print(myvalue);
}
Now I'm not sure if onDragEnd is actually allowed to be Future<void>, but let me know if it isn't
Just await for the value. It will return Dog and not instance of Future.
String someName=await myvalue;
As the value is Future, await keyword will wait until the task finishes and return the value

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

Dart: I made a function async but still can't use await

I made a function async but still can't use await expression. What am I wrong? My code is like this.
Future<void> _aFunction() async {
DocumentReference docRef = Firestore.instance.collection('collection').document(docId);
docRef.get().then((DocumentSnapshot docSnapShot) {
if (docSnapShot.exists) {
String ip = await Connectivity().getWifiIP();
That's because here is an internal (anonymous) function declaration inside then, which is not async. Actually, await keyword can be thought as a syntactic sugar over then, so it would be convenient to refactor the function like this:
Future<void> _aFunction() async {
final DocumentSnapshot snapshot = await Firestore.instance.collection('collection').document(docId).get();
if (snapshot.exists) {
String ip = await Connectivity().getWifiIP();
// Rest of the `ip` variable handling logic function
}
// Rest of the function
}
Now await keyword corresponds to the _aFunction itself instead of an anonymous function declared inside _aFunction, so it works as expected.

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

How to wait for a value by using Await

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