How to store result of a Future for later use - dart

Suppose our goal is to build a List which we will call storedResult.
We have a Future<List> returning function called multiple times:
Future<List> getList()...
Suppose that we will call our getList() function for the unknown multiple times and want to store the result of each List into our storedResult:
List storedResult = [];
someOtherList.forEach((element)){
getList().then((resultingList)){
storedResult.addAll(resultingList);
}
}
print(storedResult);
If we run this code, it will print as []. How do we express this so that the result is rather:
[resultingList, resultingList, ..., ]

Remember that Futures return immediately, but their associated callbacks run asynchronously. Specifically, the work that getList() does (and everything that then does) do not happen until execution returns to the event loop.
Your problem is that you're trying to print the result of an asynchronous operation synchronously, and that won't work.
If you want to use the stored result, then what you should do is something like:
Future<List<List>> globFutures(List someOtherList) {
List<Future> futures = [];
someOtherList.forEach((element) {
futures.add(getList());
});
// Automatically completes to a List containing all the
// results of all of the futures.
return Future.wait(futures);
}
main() {
var storedResults = [];
globFutures(someOtherList)
.then((List<List> results) {
storedResults = results;
// Run code in here that depends on storedResults being available.
print(storedResults);
});
// Do **NOT** use storedResults here, as it will be [].
// The Future functions have not yet run!
assert(storedResults == []);
};

Related

Dart/Flutter - "yield" inside a callback function

I need to yield a list for a function; however, I want to yield the list from within a callback function, which itself is inside the main function - this results in the yield statement not executing for the main function, but rather for the callback function.
My problem is very similar to the problem that was solved here: Dart Component: How to return result of asynchronous callback? but I cannot use a Completer because I need to yield and not return.
The code below should describe the problem better:
Stream<List<EventModel>> fetchEvents() async* { //function [1]
Firestore.instance
.collection('events')
.getDocuments()
.asStream()
.listen((snapshot) async* { //function [2]
List<EventModel> list = List();
snapshot.documents.forEach((document) {
list.add(EventModel.fromJson(document.data));
});
yield list; //This is where my problem lies - I need to yield for function [1] not [2]
});
}
Instead of .listen which handles events inside another function you can use await for to handle events inside the outer function.
Separately - you might want to reconsider the pattern when you yield List instances that are still getting populated inside an inner stream callback...
Stream<List<EventModel>> fetchEvents() async* {
final snapshots =
Firestore.instance.collection('events').getDocuments().asStream();
await for (final snapshot in snapshots) {
// The `await .toList()` ensures the full list is ready
// before yielding on the Stream
final events = await snapshot.documents
.map((document) => EventModel.fromJson(document.data))
.toList();
yield events;
}
}
I would like to add a suggestion for improvement here. The suggested await for solution should be avoided in some cases as it is non dismissible listener and it newer stops listening so this might lead to memory leaks. You could as well use .map to transform the stream yield results like so (havent tried to compile it, but main idea should be clear):
Stream<List<EventModel>> fetchEvents() { // remove the async*
Firestore.instance
.collection('events')
.getDocuments()
.asStream()
.map((snapshot) { // use map instead of listen
List<EventModel> list = List();
snapshot.documents.forEach((document) {
list.add(EventModel.fromJson(document.data));
});
return list; // use return instead of yield
});
}

Async Future, How to send an event back to caller that a single item has been fetched?

I'm making a Future method that lives inside a seperate class, that fetches a bunch XKCD comics, and puts them in a List and returns it.
And that is all fine and dandy, but I would like to notify back when a single comic has been fetched, so I can show a progress dialog, on how far we are.
This is my code:
// This is inside my class ComicManager
Future<List<ComicModel>> generateComicList() async {
List<ComicModel> comicList = new List<ComicModel>();
ComicModel latestComic = await getLatestComic();
for (var i = 1; i <= latestComic.num; i++) {
try {
http.Response response =
await http.get('https://xkcd.com/${i}/info.0.json');
Map comicmap = json.decode(response.body);
var comic = new ComicModel.fromJson(comicmap);
comicList.add(comic);
print(comic.num);
// Notify here that we have fetched a comic
} catch (ex) {
// Comic could apparently not be parsed, skip it.
}
}
return comicList;
}
How should I solve this?
There seems no particularly elegant way to do this. From some flutter code samples, it seems using VoidCallBack listeners is an accepted way.
First register callback functions in a Set
Set<VoidCallBack> listeners
Then define the callback functions you needed. And add them to the set
void fun()
//...
listeners.add(fun);//Or you can define a method to do this or simply pass the function through the constructor of this class.
Finally, write a notifyListeners function or its equivalent and call it wherever you want
void notifyListeners(){
for(final listener in listeners){
listener();
}
}
If you want callback functions to carry an argument, just change the VoidCallBack to whatever function types.
Found a solution.
I just used Streams like so:
Stream<ComicProgressModel> getAllComicsStream() async* {
// Do what you need to do here
// This will respond back when you are listening to the stream
yield stuffToYield; // Can be anything, and you can yield as many times you want
// When you reach the end of the method, the onDone method will be called.
// So if you are running a for loop, and call yield multiple times it onDone is only called the the this method ends
}
Then I can just listen to events like so:
Stream comicStream =
ComicManager().getAllComicsStream().asBroadcastStream();
StreamSubscription comicsub = comicStream.listen((onData) {
// Do what i need
});
Super easy to be honest.

Sequential processing of a variable number of async functions in Dart

I need to repeatedly call an asynchronous function in Dart, let's call it expensiveFunction, for a variable number of arguments. However, since each call is quite memory consuming, I cannot afford to run them in parallel. How do I force them to run in sequence?
I have tried this:
argList.forEach( await (int arg) async {
Completer c = new Completer();
expensiveFunction(arg).then( (result) {
// do something with the result
c.complete();
});
return c.future;
});
but it hasn't had the intended effect. The expensiveFunction is still being called in parallel for each arg in argList. What I actually need is to wait in the forEach loop until the expensiveFunction completes and only then to proceed with the next element in the argList. How can I achieve that?
You'll want to use a classic for loop here:
doThings() async {
for (var arg in argList) {
await expensiveFunction(arg).then((result) => ...);
}
}
There are some nice examples on the language tour.

How to return a function value from inside an inner function/stream listener?

I was writing a function in dart that would delete an object from a browser-side Indexed DB, when I discovered that I had to return an outer function value from within an inner function:
Future<bool> delete() {
Transaction tx = db.transactionStore(storeName, "readwrite");
ObjectStore os = tx.objectStore(storeName);
os.delete(_key); // returns blank future, modifies tx
// This is not correct, but shows the idea:
if (tx.onComplete) {return true;}
if (tx.onError) {return false;}
}
This function is a method for a class that I am using to save and load to the Indexed DB.
I want this function to return true or false, or a Future object containing the same, when the delete operation succeeds or fails. However, the bottleneck is the os.delete(_key); statement: it returns a future, but the actual success or failure of the delete operation is provided by tx.onComplete and tx.onError. Both of these Objects are streams, so I need to create anonymous functions that handle events from them:
tx.onComplete.listen((e){
return_to_outer_function(true);
});
tx.onError.listen((e){
return_to_outer_function(false);
});
return_to_outer_function(bool) {
return bool; // doesn't work
}
As you can see, when I create anonymous functions, the return statement no longer completes the method, but the inner function. I could have the inner functions call other functions, but then those other functions have return statements of their own that don't return a result to the whole method.
I tried the approach of setting temporary variables and periodically checking them, but it's a very inelegant solution that I don't want to have to use, not just for potential bugs, but because it would hog up the single threaded event loop.
Is it possible to return a value to an outer function from an inner function? Or is there some other, better way to get a value from the presence or absence of events from a set of streams? Or is there another way of using IndexedDB that will avoid this problem?
You can use a Completer for this.
Future<bool> delete() {
Completer completer = new Completer();
Transaction tx = db.transactionStore(storeName, "readwrite");
ObjectStore os = tx.objectStore(storeName);
tx.onError.first((e){
//return_to_outer_function(false);
completer.complete(false);
});
tx.onComplete.first(bool) {
//return bool; // doesn't work
completer.complete(true)
}
os.delete(_key); // register listeners and then do delete to be on the save side
return completer.future;
}
you then call it like
delete().then((success) => print('succeeded: $success'));
see also https://api.dartlang.org/apidocs/channels/be/dartdoc-viewer/dart:async.Completer

Waiting for Futures raised by other Futures

I'm using the Lawndart library to access browser data, and want to collect the results of a set of queries. Here's what I thought should work:
numberOfRecordsPerSection(callback) {
var map = new Map();
db_sections.keys().forEach((_key) {
db_sections.getByKey(_key).then((Map _section) {
int count = _section.length;
map[_key] = count;
});
}).then(callback(map));
}
However, when the callback is called, map is still empty (it gets populated correctly, but later, after all the Futures have completed). I assume the problem is that the Futures created by the getByKey() calls are not "captured by" the Futures created by the forEach() calls.
How can I correct my code to capture the result correctly?
the code from How do I do this jquery pattern in dart? looks very similar to yours
For each entry of _db.keys() a future is added to an array and then waited for all of them being finished by Future.wait()
Not sure if this code works (see comments on the answer on the linked question)
void fnA() {
fnB().then((_) {
// Here, all keys should have been loaded
});
}
Future fnB() {
return _db.open().then((_) {
List<Future> futures = [];
return _db.keys().forEach((String key_name) {
futures.add(_db.getByKey(key_name).then((String data) {
// do something with data
return data;
}));
}).then((_) => Future.wait(futures));
});
}

Resources