await doesnt work with stream for me in flutter - flutter

I have a problem where i want to read some data from database and i want my function to wait for the data before proceeding executing the rest of my code. i am using stream with await and async but doesnt look like it is working for me.
here is my code
void updateIncome() async {
Stream<List<IncomeData>> _currentEntries;
_currentEntries = database.watchIncomeForUpdate(this.income);
await _currentEntries.forEach((List<IncomeData> x) {
x.forEach((element) {
print('AWAIT');
}
);
});
print('FINISH');
}
here is the procedure that call my database and get data
Stream<List<IncomeData>> watchIncomeForUpdate(IncomeData entry) {
return (select(income)..where((t) =>t.id.isBiggerOrEqualValue(entry.id) & t.groupId.equals(entry.groupId))
..orderBy([(t) => OrderingTerm(expression: t.dateReceived)])).watch();
}
when i run the function updateIncome(), it prints FINISH first which make me believe that the await/async is not working by waiting for the foreach to loop through all elements in the list.
i tried to move the await keyword in the function call
_currentEntries = await database.watchIncomeForUpdate(this.income);
i get a warning message: await applied to Stream<List> which i not a Future
can someone help me? what i am doing wrong?
i want to wait for database to get the data, loop and print AWAIT then when finish, it should proceed with rest of code and print FINISH. the function that call the database return 8 rows. so when i loop using foreach, it should print AWAIT 8 times follow by FINISH.
how can i fix my code so that the function calls the database ,loop through the elements and wait until the loop finish before proceeding with the rest of the code outside of the loop?

Since watchIncomeForUpdate is not a Future function, you can't wait for a non-future function.
void updateIncome() async {
await for(var x in database.watchIncomeForUpdate(this.income)){
x.forEach((element) {
print('AWAIT');
}
);
});
print('FINISH');
}
Ref: https://dart.dev/tutorials/language/streams

Thanks for all replies. i figured it out.
i changed function from this
Stream<List<IncomeData>> watchIncomeForUpdate(IncomeData entry) {
return (select(income)..where((t) =>t.id.isBiggerOrEqualValue(entry.id) & t.groupId.equals(entry.groupId))
..orderBy([(t) => OrderingTerm(expression: t.dateReceived)])).watch();
}
to this
Future<List<IncomeData>> watchIncomeForUpdate(IncomeData entry) async {
return (select(income)..where((t) =>t.id.isBiggerOrEqualValue(entry.id) & t.groupId.equals(entry.groupId))
..orderBy([(t) => OrderingTerm(expression: t.dateReceived)])).get();
}
then call the procedure as
data = await database.watchIncomeForUpdate(this.income);

Related

What exactly await does internally in Dart?

I'm working on a flutter application which requires using local storage. As the document directory path in Android will be given in Future<Directory>, I have to check whether the future finishes or not, each time I want to use the path.
The code may be similar to below
class DataStructure {
late Future<Directory> _dir = getApplicationDocumentsDirectory();
Future<void> read(String fileName) async {
Directory dir = await _dir;
// Do something using dir
}
}
This might be stupid, but I write C++ code in most of the time, so I want to reduce number of times pushing a function to the event queue for a better performance (I guess the compiler cuts async functions with await into "functions" to be pushed to the event queue). So I wrote experiment code to check whether await on a finished future will cut the control flow or not. (I mean, in the newest version of Dart, async functions will execute until the first 'await' keyword arises. I'm wondering if this is still the case if the future has already been finished.)
Future<void> voidFuture(String name) async {
print('$name: voidFuture');
}
Future<void> printSingle(String name, int index) async {
print('$name: -- index = $index');
}
// This emit function will print one line each time
// it gets chance to be executed.
// Because the 'await' keyword will cut the control flow,
// it can only print once each time it's executed,
// and must wait for the next chance to print again.
// This feature makes it appropriate for testing
// when and where the control flow is cut,
// as each cut will lead to one line of output.
Future<void> emit(String name, int count) async {
for (int index = 0; index != count; ++index) {
await printSingle(name, index);
}
}
Future<void> task_0() async {
const String name = 'task_0';
Future<void> emitFinish = emit(name, 3);
await voidFuture(name);
print('$name: output after await');
await emitFinish;
}
Running task_0 in my environment (Dart SDK version: 2.18.5 (stable) on "windows_x64") gives output as below:
task_0: -- index = 0
task_0: voidFuture
task_0: -- index = 1
task_0: output after await
task_0: -- index = 2
Which is the same as what I expected. And the weird thing comes when I change the emit() function:
Future<void> emit(String name, int count) async {
for (int index = 0; index != count; ++index) {
// Before:
// await printSingle(name, index);
// After:
await Future(() {
print('$name: -- index = $index');
});
}
}
Then the output becomes
task_0: voidFuture
task_0: output after await
task_0: -- index = 0
task_0: -- index = 1
task_0: -- index = 2
and it makes no sense for me that the third line, -- index = 0 comes after output after await. It seems that a function is more privileged than a future from constructor?
And my main question is "Will 'await' waits for a finished future?", so I wrote code below:
Future<String> stringFuture() async {
return '.';
}
Future<void> task_3() async {
const String name = 'task_3';
Future<void> emitFinish = emit(name, 4);
Future<String> futureString = stringFuture();
print('$name: before await of futureString');
await futureString;
print('$name: 1st await of futureString over');
await 1;
print('$name: 1st await of constant over');
await 2;
print('$name: 2nd await of constant over');
await emitFinish;
}
With the first version of emit(), the output is
task_3: -- index = 0
task_3: before await of futureString
task_3: -- index = 1
task_3: 1st await of futureString over
task_3: -- index = 2
task_3: 1st await of constant over
task_3: -- index = 3
task_3: 2nd await of constant over
Which means even await for a constant integral will push the lines after it to the event queue.
(Of course, with the second version of emit() all its output comes after the last print() in task_3() , and I don't know why)
I know there are manys work-arounds, one of them will be using a T? value to be assigned after the first time the Future<T> finishes, and check whether value == null each time using it.
But the questions I want to ask are:
What does the keyword await do internally? Please come with details that are enough to explain phenomena above.
Is there any way of overriding the default await behavior? E.g., by overriding a method?
What's the preferred way of using a Future value for many times?
(Unrelated to above) How to stop at the welcome page in Flutter to wait for these async functions, e.g.,
getApplicationDocumentsDirectory()
to finish before building all the widgets?
Most results I got from Google were introduction on async and await keywords for beginners, and I couldn't find much material explaining the behavior of await in Dart API Reference Documentation.
Thank you for saving a heart broken by await >_<
await is syntactic sugar for registering callbacks through the Future API. For example:
Future<int> foo() async {
var x = await someIntFuture();
return otherStuff(x);
}
is basically transformed into:
Future<int> foo() {
return someIntFuture.then((x) {
return otherStuff(x);
});
}
await registers a Future.then callback and returns that new Future. That it returns a Future means that await always yields (even in cases such as await null). This is also why when you invoke an async function, its body is executed synchronously until it reaches its first await.
it makes no sense for me that the third line, -- index = 0 comes after output after await. It seems that a function is more privileged than a future from constructor?
From the documentation for the Future constructor:
Creates a future containing the result of calling computation asynchronously with Timer.run.
The callback you supply to the Future constructor is invoked asynchronously; it is scheduled. This is different from calling an async function, which as stated before executes synchronously as much as possible first.
And my main question is "Will 'await' waits for a finished future?"
It doesn't matter if the Future is already completed or not. await always yields.
Is there any way of overriding the default await behavior? E.g., by overriding a method?
As mentioned, await is syntactic sugar. What you could do is to create a class that implements the Future API and handles .then differently (which is what Flutter's SynchronousFuture class does), but I wouldn't recommend it (for the same reasons why the SynchronousFuture documentation discourages its use).
What's the preferred way of using a Future value for many times?
Depends on the situation. In general, try to await the Future once and store the result somewhere (such as in a local variable). Otherwise I'd just await the Future multiple times and not worry about it until there's evidence that it's performance-critical.
(Unrelated to above) How to stop at the welcome page in Flutter to wait for these async functions
Depends. For some things, you can simply make your main function async and await whatever asynchronous initialization you want to do before calling runApp. In other cases (particularly for long-running ones), you should use a FutureBuilder.
(Also, in the future, separate questions should be asked separately.)

Function not waiting for an async function [duplicate]

This question already has an answer here:
How to wait for forEach to complete with asynchronous callbacks?
(1 answer)
Closed 11 months ago.
I need that the function _get_datos_restaurante() waits to the other functions (_get_nombre_provincia() and _get_valoracion_media() ) ends, but I can't achieve that.
The issue is the variable valoracion_media is not correctly "operated" when _get_datos_restaurate() ends. The functions are asynchronous and I am using _get_datos_restaurante() in a FutureBuilder, so I don't know what it's the error.
Here is my code:
Future<void> _get_datos_restaurante(String id, QueryDocumentSnapshot r) async {
await _get_nombre_provincia(id);
await _get_valoracion_media(r);
print(valoracion_media);
}
Future<void> _get_nombre_provincia(String id) async {
await firestoreInstance.collection('Provincia').doc(id).get().then((value) => nombreProvincia = value.get('nombre'));
}
Future<void> _get_valoracion_media(QueryDocumentSnapshot r) async {
List<dynamic> id_valoraciones = r.get('valoraciones');
List<double> nota_valoraciones = [];
id_valoraciones.forEach((v) async {
await firestoreInstance.collection('Valoracion_Restaurante').doc(v).get().then((value) {
nota_valoraciones.add(value.get('nota'));
});});
nota_valoraciones.forEach((n) =>valoracion_media+=n);
valoracion_media = valoracion_media/nota_valoraciones.length;
}
Use a for loop instead of forEach like so:
for(final v in id_valoraciones){
final x = await firestoreInstance.collection('Valoracion_Restaurante').doc(v).get();
nota_valoraciones.add(x.get('nota'));
}
You have a forEach loop where you iterate over id_valoraciones which cannot wait for Futures to complete.
In order to wait for several futures at once, you can use Future.wait, which waits for several futures to complete and collect the results.
You can use map for turning a list of items into a list of Futures, which can then be waited for.
nota_valoractiones = await Future.wait(id_valoraciones.map((v) async {
const value = await firestoreInstance.collection('Valoracion_Restaurante').doc(v).get();
return value.get('nota');
});
This code can be broken down as follows:
For each entry in id_valoraciones, create a Future that...
Gets a document value from firestore
Returns the nota field from that document
Waits for all the Futures to complete, saving the results of them in the list nota_valoractiones.
In other words, the Futures execute in parallel.
As highlighted by #pskink in a comment to the question, you can also use Future.forEach to perform an asynchronous action for each entry in a list. Note, however, that Future.forEach does not execute in parallel.

Flutter web Future .then/.whenComplete

Please help me understand, why this code not working!
I try to get data from a Stream (Firestore), and take this data to a list. I want to wait until the list is ready, and with this list do something. But .then or .whenComplete fires before the list is ready...
This is the function to make the list and return it:
Future<List<EventDistance>> getEventsDistanceList(String eventId) async{
Stream<FS.QuerySnapshot> qs = EventDistanceDataRepository().getStreamByEventId(eventId: eventId);
List<EventDistance> dList = [];
EventDistance eventDistance;
qs.forEach((document) {
document.forEach((docs) {
eventDistance = eventDistanceFromJson(docs.data());
dList.add(eventDistance);
print(eventDistance.Name); //(3.) only for testing, to see if docs is not empty
}
);
});
print('return'); //(1.) only for testing, to see when return is fired
return dList;
}
(return also fires before)
i use this code so:
Future<List<EventDistance>> dList = getEventsDistanceList(filteredList[index].id );
dList.then((value) {
print('value: $value'); //(2.) only for testing,to see if the returned list is empty or not (empty :-( )
doSomething;
});
When i run, i recive first 'return' (1.), then 'value: null' (2.) (and an empty list) and then the elements of the list (Name1, Name2 ...) (3.).
What do i wrong? How to wait to receive the list first?
Thanks for the answeres!
To become more confident with async operations read the perfect
article by Didier Boelens
Let check what is going on in your code
Your getEventsDistanceList() routine is pure synchronous - all of it's content runs synchronously step by step
synchronously subscribe to a Stream in qs.forEach and set callback listener (document) { ... } which will be fired on each stream item somewhere in future
synchronous call print('return') is fired
finally getEventsDistanceList() returns
you listen to this Future returned from getEventsDistanceList() until it complete and then then() is fired with call to print('value: $value')
first stream item is received and callback fired with print(eventDistance.Name)
5th step will repeat with new items until stream completes or ended with error (see Stream.forEach implementation)
I supposed you need only first Stream item (if not, do not hesistate reach me in comments)
If so rewrite your code
EventDistanceDataRepository()
.getStreamByEventId(eventId: eventId)
.first
.then((document) => document.map((docs) => eventDistanceFromJson(docs.data())).toList())
.then((value) { doSomething;});
I prefer more readable await notation
final FS.QuerySnapshot document = await EventDistanceDataRepository()
.getStreamByEventId(eventId: eventId)
.first;
final List<EventDistance> listOfEvents = document.docs.map((e) => eventDistanceFromJson(e.data())).toList();
doSomething with this list
You need to use await in asynchronous functions. I'm guessing
Stream<FS.QuerySnapshot> qs =
EventDistanceDataRepository().getStreamByEventId(eventId: eventId);
Should be
Stream<FS.QuerySnapshot> qs = await
EventDistanceDataRepository().getStreamByEventId(eventId: eventId);
Where ever the operation that takes a long time happens gets the await keyword.
Try the code labs to get better with async await
works fine! the final code is:
final List<EventDistance> listOfEvents = document.docs.map((e) => eventDistanceFromJson(e.data())).toList();

Why does my async function that returns a Future<int> stall at await for each?

My goal with this function is to return an integer representing all of the habits completed by the user. My databases structure is a collection of habits, each with a sub-collection containing its history. The function below utilizes a list of habits, and using the habit's ID it gets a stream of the history for that habit. My problem is that when I try to loop through the history for each habit it stalls at the await for. Any advice would be appreciated, I am still trying to fully understand streams/asynchronous functions.
Future<int> getCompleteHabits(User user, List<Habit> habits) async {
int completed = 0;
if(habits.isNotEmpty) {
for(Habit habit in habits) {
Stream<List<HabitHistory>> streamHistory = db.streamHabitHistory(user, habit.id);
await for(var h in streamHistory){
print(habit.title);
print(h);
}
print("test");
}
}
return completed;
}
This function will add to the variable completed, however, since I cannot access all the history I have yet to do so.
This function prints the following to the console, it doesn't get to the print("test").
flutter: Get in bed by 11:30 pm
flutter: [Instance of 'HabitHistory', Instance of 'HabitHistory', Instance of 'HabitHistory', Instance of 'HabitHistory', Instance of 'HabitHistory']
From the documentaion on Streams:
Streams are done when there are no more events in them, and the code receiving the events is notified of this just as it is notified that a new event arrives. When reading events using an await for loop, the loops stops when the stream is done.
The reason your loop is hanging is that the stream hasn't yet been closed. It will continue to hang until the stream gets closed or until you execute a break or return statement within the loop.
As Abion47 stated the stream has not been closed so the await for is never complete. It is waiting for new events.
Knowing this I can use the await streamHabitHistory(user, habit.id).first to get the list (so it is not waiting for events). The code is below.
Future<int> getCompleteHabits(User user, List<Habit> habits) async {
int completed = 0;
if(habits.isNotEmpty) {
for(Habit habit in habits) {
List<HabitHistory> history = await db.streamHabitHistory(user, habit.id).first;
for (HabitHistory h in history) {
completed = completed + h.completed.length;
}
}
}
return completed;
}

How Do I convert Stream<QuerySnapshot> to List<myObject>

I'm querying Firestore and getting a Stream back as a Stream of QuerySnapshots. I need to map the included Documents in the stream to a List of objects.
The code below doesn't work (obviously)...maybe I'm just looking at this entirely wrong.
List<UserTask> getUserTaskList() {
List<UserTask> list;
Stream<QuerySnapshot> stream =
Firestore.instance.collection('userTasks').snapshots();
stream.listen((snapshot) {
snapshot.documents.forEach((doc) {
UserTask userTask = UserTask(
doc.data['id'],
doc.data['Description'],
etc...);
list.add(userTask);
});
});
return list;
}
With the code above, since it doesn't wait for the entire stream (or any of it actually), list is always returned as null. In short, how do I convert my stream to a List?
Note: I'm pretty new to the world of Dart, so go easy on me :)
Thanks!
First of all, think about this: this function has to return very quickly. All functions do, otherwise UI would hang. However, you are expecting the function to return something that comes from the internet. It takes time. The function has to return. There is no way for a function to simply do a network request and return you the result. Welcome to the world of asynchronous programming.
Furthermore, the stream you have is not a stream of DocumentSnapshots (which you can convert to UserTasks), but a stream of QuerySnapshots (which you can convert to List<UserTask>s). Notice the plural there. If you simply want to get all your UserTasks once, you should have a Future instead of a Stream. If you want to repeatedly get all your UserTasks after each change, then using a Stream makes sense.
Since you said you want to get a List<UserTask>, I'm assuming you want to get the collection of UserTasks only once.
Here's what your code becomes in this light:
Future<List<UserTask>> getUserTaskList() async {
QuerySnapshot qShot =
await Firestore.instance.collection('userTasks').getDocuments();
return qShot.documents.map(
(doc) => UserTask(
doc.data['id'],
doc.data['Description'],
etc...)
).toList();
}
main() async {
List<UserTask> tasks = await getUserTaskList();
useTasklist(tasks); // yay, the list is here
}
Now if you really wanted to use a stream, here's how you could do it:
Stream<List<UserTask>> getUserTaskLists() async {
Stream<QuerySnapshot> stream =
Firestore.instance.collection('userTasks').snapshots();
return stream.map(
(qShot) => qShot.documents.map(
(doc) => UserTask(
doc.data['id'],
doc.data['Description'],
etc...)
).toList()
);
}
main() async {
await for (List<UserTask> tasks in getUserTaskLists()) {
useTasklist(tasks); // yay, the NEXT list is here
}
}
Hope it helps.