Is it possible to pass an asynchronous function into a stream.map() function?
final CollectionReference myCollection =
FirebaseFirestore.instance.collection('foo');
Future<Stream<MyModel>> get myStream async {
// incorrect code, but I want to do something like this
return await myCollection.snapshots().map(_mappingFunc);
}
Future<MyModel> _mappingFunc(QuerySnapshot snapshot) async {
// some async code
}
That's not possible, since the declaration of the map is the following:
Stream<S> map <S>(
S convert(
T event
)
)
map takes a convert function which is of type S, basically the type of the Stream used.
You can use the asyncMap() method:
https://api.dart.dev/stable/2.9.1/dart-async/Stream/asyncMap.html
You can either use asyncMap to perform the mapping which outputs to a Stream or you can wrap your function and await the result to use an asynchronous function with List.map to get a List result:
myCollection.snapshots().map((e) async => await _mappingFunc(e));
Another option is to use an async* method to achieve a Stream output:
Stream<MyModel> get myStream async* {
for (final snapshot in myCollection.snapshots()) {
yield await _mappingFunc(snapshot);
}
}
Related
For an instance, let's suppose I am calling a function in initState which gets some documents from the Firebase collection. I am iterating on those documents using async forEach and need to perform an await operation to get some data from another collection from firebase and saving them in a list then returning after the forEach is finished. But the returned list is empty as the second await function completes after return statement. How do I handle this? I have a hard time understanding Asynchronous programming so please a detailed explanation will be highly appreciated.
The code here is just an example code showing an actual scenario.
Future getList() async {
//These are just example refs and in the actual refs it does print actual values
var collectionOneRef = Firestore.instance.collection('Collection1');
var collectionTwoRef = Firestore.instance.collection('Collection2');
List<Map<String, dynamic>> properties = [];
QuerySnapshot querySnapshot = await collectionOneRef
.getDocuments()
.then((query) {
query.documents.forEach((colOneDoc) async {
var colOneID = colOneDoc.documentID;
await collectionTwoRef.document(colOneID).get().then((colTwoDoc) {
Map<String, dynamic> someMap = {
"field1": colTwoDoc['field1'],
"field2": colTwoDoc['field2']
};
properties.add(someMap);
properties.sort((p1, p2) {
//sorting based on two properties
var r = p1["field1"].compareTo(p2["field1"]);
if (r != 0) return r;
return p1["field2"].compareTo(p2["field2"]);
});
print(properties); //this prints actual data in it
));
});
});
});
print(properties); //This prints a blank list as [] and obviously returns blank list
return properties;
}
And now when I call this function in an initState of a stateful Widget and display it somewhere, it display a blank list. BUT, after I "Hot Reload", then it displays. I want to get the data and display it without hot reloading it. Thanks in advance
Sounds like you need to await the whole function itself in the block of code where you are calling getList().
you are async'ing and awaiting inside the function, and that looks fine which is why you are getting results when hot reloading. But it might be the case that you're not actually awaiting the entire function itself in the code that is calling this. Please Check.
Having the following code:
Future<String> checkPrinter() async {
await new Future.delayed(const Duration(seconds: 3));
return Future.value("Ok");
}
String getPrinterStatus() {
checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_) {
return "Printer does not respond!";
});
}
void main() {
print(getPrinterStatus());
}
The output is "null" because the function getPrinterStatus() returns without waiting for checkPrinter to complete (correctly i have a warning telling me that getPrinterStatus does not return a string).
What should i do to make getPrinterStatus to wait for checkPrinter()?
Future<String> getPrinterStatus() async {
await checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_){
return "Printer does not respond!";});
}
There are no such way to make async code to run in a synchronous way in Dart. So you need to make it so async methods returns a Future so it is possible to await the answer.
The problem you have in your code is your are using the then method but does not take into account that this method returns a Future you should return. So e.g.:
String getPrinterStatus() {
checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_){
return "Printer does not respond!";});
}
The two return statements in this example are used for the method your are giving as argument for then() and are therefore not used to return from getPrinterStatus().
Instead, then() returns a Future<String> which will complete with the value.
So you need to carry the async through your program. Your code could make use of some language features you gets from marking a method async so I have tried to rewrite your code so it works as expected and make use of this features:
Future<String> checkPrinter() async {
await Future.delayed(const Duration(seconds: 3));
return "Ok";
}
// Just an example that the method could be written like this.
// Future.delayed takes a computation as argument.
Future<String> checkPrinterShorter() =>
Future.delayed(const Duration(seconds: 3), () => 'Ok');
Future<String> getPrinterStatus() async {
try {
return 'The printer answered: ${await checkPrinter()}';
} catch (_) {
return "Printer does not respond!";
}
}
Future<void> main() async {
print(await getPrinterStatus());
}
There are some changes I think you should notice:
If a method is marked as async the returned value will automatically be packed into a Future<Type> so you don't need to return Future.value("Ok") but can just return Ok.
If a method is marked as async you can use await instead of using then(). Also, you can use normal exception handling so I have rewritten getPrinterStatus to do that.
Your getPrinterStatus() is not async. main() function will never wait for it's execution.
Make it async. Refactor your main() for something like
void main() {
getPrinterStatus().then((value) {
print(value);
}).catchError((_){
return "Printer does not respond!";});
}
I've encountered a weird issue where if I yield* from my provider in my flutter app, the rest of the code in the function doesn't complete.
I'm using the BLoC pattern, so my _mapEventToState function looks like this:
Stream<WizardState> _mapJoiningCongregationToState(
int identifier, int password) async* {
_subscription?.cancel();
_subscription= (_provider.doThings(
id: identifier, password: password))
.listen((progress) => {
dispatch(Event(
progressMessage: progress.progressText))
}, onError: (error){
print(error);
}, onDone: (){
print('done joiining');
});
}
Then in the provider/service... this is the first attempt.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
yield* _progressStream.stream;
}
The yield statement returns, but only after both awaited functions have completed. This makes complete sense to me, obviously I wouldn't expect the code to complete out of order and somehow run the yield* before waiting for the 'await's to complete.
In order to "subscribe" to the progress of this service though, I need to yield the stream back up to the caller, to write updates on the UI etc. In my mind, this is as simple as moving the yield* to before the first await. Like this.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
yield* _progressStream.stream;
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}
But, then setting breakpoints on the later _progressStream.add calls show that these never get called. I'm stuck on this, any idea what it could be? I know it has something to do with how I have mixed Futures and Streams.
The yield* awaits the completion of the stream it returns.
In this case, you want to return a stream immediately, then asynchronously feed some data into that stream.
Is anything else adding events to the stream controller? If not, you should be able to just do:
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake1...");
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake5!!!...");
}
No stream controller is needed.
If other functions also add to the stream controller, then you do need it. You then have to splut your stream creation into an async part which updates the stream controller, and a synchronous part which returns the stream. Maybe:
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) {
() async {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}(); // Spin off async background task to update stream controller.
return _progressStream.stream;
}
I have several questions regarding the use of Futures in dart. Let's say I am working with a firestore and I have a function like this to update users's info :
void updateOldUser(User oldUser,String newInfo){
DocumentReference userToUpdateRef = userRef.document(oldUser.id);
Firestore.instance.runTransaction((Transaction transaction) async {
DocumentSnapshot userToUpdateSnapshot = await transaction.get(userToUpdateRef);
if(userToUpdateSnapshot.exists){
await transaction.update(
userToUpdateSnapshot.reference, userToUpdateSnapshot.data[newInfo] + 1
).catchError((e)=> print(e));
}
});
}
My question is : does it need to return a future since runTransaction is Future. It seems to work fine without it but to me it feels like it should return a "Future void" in order to be able to await updateOldUser when I use it. But when I turn it into a "Future void" and then end the function body with 'return;' I have an error saying 'expected a value after return'.
But what I really don't understand is that with another similar code :
Future<void> updateUserPhoto(User user,File userPhoto) async {
String photoUrl = await uploadImage(user.id,userPhoto);
DocumentReference userToUpdateRef = userRef.document(user.id);
Firestore.instance.runTransaction((Transaction transaction) async {
DocumentSnapshot userToUpdateSnapshot = await transaction.get(userToUpdateRef);
if(userToUpdateSnapshot.exists){
await transaction.update(
userToUpdateSnapshot.reference, {
'photoUrl' : photoUrl
}
).catchError((e)=> print(e));
}
});
return;
}
I don't get this error and It work fine also. Why ? Thanks in advance.
does it need to return a future
If you want the caller to be able to await the completion of the function, then the return type should be Future<...> (like Future<void> if there is no concrete return value, or Future<int> if the result is an integer value, ...)
For fire-and-forget async functions you can use void, but that is rather uncommon.
If the return type is Future<...> the caller can still decide not to wait for completion anyway.
But when I turn it into a "Future void" and then end the function body with 'return;'
If you use async, a Future is returned implicitly at the end of the method.
If you do not use async, you need to return a Future like
return doSomething.then((v) => doSomethingElse());
So, I have a map which has to do with some asynchronous processing using the items inside. I used the forEach loop construct and inside the callback is designed to be async because I call an await inside the iteration body
myMap.forEach((a, b) { await myAsyncFunc(); } );
callFunc();
I need the callFunc() to be called after all the items have been iterated. But the forEach exits immediately. Help!
Use a for loop over Map.entries instead of forEach. Provided you are in an async function, await-ing in the body of a for loop will pause the iteration. The entry object will also allow you to access both the key and value.
Future<void> myFunction() async {
for (var entry in myMap.entries) {
await myAsyncFunction(entry.key, entry.value);
}
callFunc();
}
You can also use Future.forEach with a map like this :
await Future.forEach(myMap.entries, (MapEntry entry) async {
await myAsyncFunc();
});
callFunc();
You could also use map like:
const futures = myMap.map((a, b) => myAsyncFunc());
await Future.wait(futures);
callFunc();
entries used in extract Maplist from HashMap.
products.entries.forEach((e) {
var key = e.key;
var values = e.value;
double sum = 0;
values.forEach((value) => sum += value[PlanogramShelf.SHELF_FULL]);
target.add(OrdinalSales(
key, double.parse((sum / valueslength).toStringAsFixed(2))));
});