I couldn't figure out why the following code didn't work correctly.
I also tried it on DartPad. The result is the same.
import 'dart:async';
Future<bool> longWait(String prefix) async {
for (int i = 0; i < 5; i++) {
print('$prefix $i');
}
return true;
}
Future testAsync() async {
print('starting');
longWait('Async');
print('done');
}
main(List<String> arguments) {
testAsync();
}
the result is:
starting
Async 0
Async 1
Async 2
Async 3
Async 4
done
but it has to be this:
starting
done
Async 0
Async 1
Async 2
Async 3
Async 4
Since Dart 2 sync code at the beginning of async functions is executed sync.
This was different in Dart 1.
A workaround would be
Future<bool> longWait(String prefix) async {
await Future.microtask((){});
for (int i = 0; i < 5; i++) {
print('$prefix $i');
}
return true;
}
The code after the await is executed async and results in the desired behavior. (tested in DartPad)
Related
So, I'm building my app in Flutter and unfortunately, I have recently come across an error. So what I want to do in my TestProvider class is to get data from firestore (what getQuestionFromFirebase() function is doing), and after that happens, I want to create a map from DocumentSnapshot (what questionMapFromFirebase() function is doing). And there comes an error, because I can't async in map function so my function doesn't wait for the result from previous function, and returns null. Any solutions? *I tried to return map from getQuestionFromFirebase() - Future, but later I can't use value from it because, my function wants pure map.
class TestProvider {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<DocumentSnapshot> getQuestionFromFirebase(String documentId) async {
return await _firestore.collection('questions').doc(documentId).get();
}
Map questionMapFromFirebase(String documentId) {
Map questionMapFromFirebase;
getQuestionFromFirebase(documentId).then((DocumentSnapshot carSnapshot) => {
questionMapFromFirebase = carSnapshot.data(),
});
return questionMapFromFirebase;
}
}
Later I'm using this function there:
I'm using this function later there
List<Question> listOfQuestions() {
List<int> range = numberInRange(amountOfQuestions);
List<Question> listOfQuestions;
for (int i = 1; i <= amountOfQuestions; i++) {
listOfQuestions.add(Question.fromMap(
_testProvider.questionMapFromFirebase(range[1].toString())));
}
return listOfQuestions;
}
And that's creating error when Future occurs.
The argument type 'Future<Map<dynamic, dynamic>>' can't be assigned to the parameter type 'Map<String, dynamic>'.
Edit:
So recently I've made some changes to my code and now it looks like that
class TestProvider {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<DocumentSnapshot> getQuestionFromFirebase(String documentId) async {
return await _firestore.collection('questions').doc(documentId).get();
}
Future<Map> questionMapFromFirebase(String documentId) async {
DocumentSnapshot ds = await getQuestionFromFirebase(documentId);
return ds.data();
}
}
and repository
class TestRepository {
final int amountOfQuestions;
TestRepository({
#required this.amountOfQuestions,
});
TestProvider _testProvider;
Future listOfQuestions() async {
List<int> range = numberInRange(amountOfQuestions);
List<Question> listOfQuestions;
for (int i = 1; i <= amountOfQuestions; i++) {
listOfQuestions.add(Question.fromMap(
await _testProvider.questionMapFromFirebase(range[i].toString())));
}
return listOfQuestions;
}
}
The problem I started to see that is that every time i tried to call function questionMapFromFirebase from TestProvider, it has been working just fine. But when i tried to call it from TestRepository it throw the error:
E/flutter (13348): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: NoSuchMethodError: The method 'questionMapFromFirebase' was called on null.
E/flutter (13348): Receiver: null
E/flutter (13348): Tried calling: questionMapFromFirebase("2")
Any other sugestions how can I handle it?
Future<Map> questionMapFromFirebase(String documentId) async {
DocumentSnapshot ds = await getQuestionFromFirebase(documentId);
return ds.data();
}
Edit
check FutureBuilder class
example, it will be inside your widget tree where the list need to be shown.
return FutureBuilder(
future: _loadQuestions(),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.done){
return widgetForListing(snapshot.data);
}
return Center(child: Text('Loading...'));
},
);
And your _loadQuestions function will be as
_loadQuestions() async {
List<int> range = numberInRange(amountOfQuestions);
List<Question> listOfQuestions = [];
for (int i = 1; i <= amountOfQuestions; i++) {
listOfQuestions.add(Question.fromMap(
await _testProvider.questionMapFromFirebase(range[1].toString())));
}
return listOfQuestions; //you can get this list in **snapshot.data** of future builder
}
I want to check if the Firebase DB is connected or not, so I have to use a Future to return a boolean
Have a check at my code..
#override
Future<bool> isAvailable() async {
bool ret = false;
await firebaseInstance.reference().child('.info/connected').onValue.listen((event) {
ret = event.snapshot.value;
});
return ret;
}
the firebaseInstace.reference is a StreamSubscription type and does not wait for the future to return me a result.
please help.
If you only need to know the current value, use once().then instead of onValue.listen
#override
Future<bool> isAvailable() async {
var snapshot = await firebaseInstance.reference().child('.info/connected').once();
return snapshot.value;
}
Instead of awaiting the end of the stream subscription (it never ends), just take the first value:
#override
Future<bool> isAvailable() => firebaseInstance.reference().child('.info/connected').onValue.first;
You can put the StreamSubcription in a variable
StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {
// you code here
if (someCondition == true) {
subscription.cancel();
}
});
More can be found here is there any way to cancel a dart Future?
You can do the following:
#override
Future<bool> isAvailable() async {
bool ret = false;
Stream<Event> events =
FirebaseDatabase.instance.reference().child('.info/connected').onValue;
await for (var value in events) {
ret = value.snapshot.value;
}
return ret;
}
onValue returns a Stream<Event> and then you can use await for to iterate inside the Stream and get the data, and then it will return.
I have refactored a Web API to rely on async/await in ASP.NET Core 3.1 and I have the following scenario: a statistics method is sequentially computing a list of indicators which are defined in a list.
readonly Dictionary<StatisticItemEnum, Func<Task<SimpleStatisticItemApiModel>>> simpleItemActionMap =
new Dictionary<StatisticItemEnum, Func<Task<SimpleStatisticItemApiModel>>>();
private void InitSimpleStatisticFunctionsMap()
{
simpleItemActionMap.Add(StatisticItemEnum.AllQuestionCount, GetAllQuestionCountApiModel);
simpleItemActionMap.Add(StatisticItemEnum.AllAnswerCount, GetAllAnswerCountApiModel);
simpleItemActionMap.Add(StatisticItemEnum.AverageAnswer, GetAverageAnswer);
// other mappings here
}
private async Task<SimpleStatisticItemApiModel> GetAllQuestionCountApiModel()
{
// await for database operation
}
private async Task<SimpleStatisticItemApiModel> GetAllAnswerCountApiModel()
{
// await for database operation
}
private async Task<SimpleStatisticItemApiModel> GetAverageAnswer()
{
// await for database operation
}
The code sequentially goes through each item and computes it and after the refactoring it is looking like this:
itemIds.ForEach(itemId =>
{
var itemEnumValue = (StatisticItemEnum) itemId;
if (simpleItemActionMap.ContainsKey(itemEnumValue))
{
var result = simpleItemActionMap[itemEnumValue]().Result;
payload.SimpleStatisticItemModels.Add(result);
}
});
I know that Task.Result might lead to deadlocks, but I could not find any other way to make this work.
Question: How to execute a dynamic list of async functions in a sequential way?
You should change the ForEach call to a regular foreach, and then you can use await:
foreach (var itemId in itemIds)
{
var itemEnumValue = (StatisticItemEnum) itemId;
if (simpleItemActionMap.ContainsKey(itemEnumValue))
{
var result = await simpleItemActionMap[itemEnumValue]();
payload.SimpleStatisticItemModels.Add(result);
}
}
Do not make the ForEach lambda async; that will result in an async void method, and you should avoid async void.
I think you can do this:
itemIds.ForEach(async itemId =>
{
var itemEnumValue = (StatisticItemEnum) itemId;
if (simpleItemActionMap.ContainsKey(itemEnumValue))
{
var result = await simpleItemActionMap[itemEnumValue]();
payload.SimpleStatisticItemModels.Add(result);
}
});
This question already has answers here:
async is snowballing to callers, can't make constructor async
(4 answers)
Closed 3 years ago.
Just kind of confused as to when I should use which? What are the differences?
Does async await not execute the next line of code in the function until done and does it get pulled out the general order of functions? If so what does then do, how does it differ?
If I wanted to make sure something was done before calling the method to get a value so it doesn't return a null which should I use?
For instance I wanted to get info from a database and then set a variable to that data as soon as the screen loads so i define that inside initState(),
#override
void initState() {
// TODO: implement initState
super.initState();
currentUser= new User();
currentUser.getInfo().then((_) =>setState(() { bio = currentUser.getBio(); print(bio); }));
}
getInfo is an async fucntion, I tried this but what ends up happening is it somehow prints null first and then later prints the actual bio which is called from inside the getinfo method. How do I switch the order?
UPDATE:
Here is the user class:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
class User {
final _firestore = Firestore.instance;
final _auth= FirebaseAuth.instance;
FirebaseUser loggedInUser;
String displayName;
String email;
String bio;
String photoUrl;
Future<void> getCurrentUser() async{
try{
final user= await _auth.currentUser();
if(user!=null){
loggedInUser=user;
email=loggedInUser.email;
}}
catch(e){
print(e);
}
}
Future<void> getInfo() async {
await getCurrentUser();
DocumentReference documentReference =
_firestore.collection("users").document("$email");
documentReference.get().then((DocumentSnapshot datasnapshot) {
if (datasnapshot.exists) {
displayName=datasnapshot.data['displayName'].toString();
bio=datasnapshot.data['bio'].toString();
print(bio);
}
else {
print("No such user");
}
});
}
User({String bio,String displayName}){
if(bio!=null){
this.bio= bio;
print(this.bio);
}
if(displayName!=null){
this.displayName = displayName;
}
}
void updateData({String bio, String displayName}){
if(bio!=null){
this.bio=bio;
print(this.bio);
}
if(displayName!=null){
this.displayName=displayName;
}
_firestore.collection('users').document('$email').setData({
'bio':this.bio,
'displayName':this.displayName
});
}
String getBio(){
return bio;
}
}
UPDATE :
changed getinfo to this and it worked now , dont really get why though:
Future<void> getInfo() async {
await getCurrentUser();
DocumentReference documentReference =
_firestore.collection("users").document("$email");
await documentReference.get().then((DocumentSnapshot datasnapshot) {
if (datasnapshot.exists) {
displayName=datasnapshot.data['displayName'].toString();
bio=datasnapshot.data['bio'].toString();
print(bio);
}
else {
print("No such user");
}
});
}
await is a keyword that can only be used in async method.
then() is a method.
Example:
Future<void> A() async {
await Future.delayed(_duration);
print("A");
}
void B() {
print("B");
}
void C() {
print("C");
}
with await
void withAwait() async {
await A();
B();
C();
}
/// Print A, B, C respectively
with then
void withThen() {
A().then((_) => B());
C();
}
/// Print C, A, B respectively
void withThen2() {
A().then((_) {
B();
C();
});
}
/// Print A, B, C respectively
I'm playing around with dart/flutter and I can't work out how to run two functions at the SAME time and wait for them to finish.
Apparently, I should use isolate.spawn but can't get anything to work, for instance:
t1(dynamic t) {
sleep(Duration(seconds: 10));
}
t2(dynamic t) {
sleep(Duration(seconds: 10));
}
main() async {
Future f1 = Isolate.spawn(getThing, null);
Future f2 = Isolate.spawn(getThing1, null);
Future.wait([f1,f2]);
}
Whilst the two functions run, the call to Future.wait doesn't wait for them to finish.
Then there's the problem of how do I deal with any return values from the functions.
Anyone?
TIA.
Your snippet does not work because
Isolate.spawn returns a future which
will complete with an Isolate instance if the spawning succeeded, not when the entry point, in this case t1 and t2, return.
To deal with the return values of the spawned functions you may use a ReceivePort. For example:
import 'dart:io';
import 'dart:isolate';
t1(port) {
sleep(Duration(seconds: 3));
port.send(["t1", 1]);
}
t2(port) {
sleep(Duration(seconds: 6));
port.send(["t2", 2]);
}
main() async {
var receivePort = new ReceivePort();
Isolate.spawn(t1, receivePort.sendPort);
Isolate.spawn(t2, receivePort.sendPort);
await for (var retMsg in receivePort) {
print("res: $retMsg");
if (retMsg[0] == "t2") {
return;
}
}
}
You might find package:isolate useful here.
import 'dart:io';
import 'package:isolate/isolate.dart';
t1(dynamic t) {
sleep(Duration(seconds: 10));
}
t2(dynamic t) {
sleep(Duration(seconds: 10));
}
void main() async {
var isolate1 = await IsolateRunner.spawn();
var isolate2 = await IsolateRunner.spawn();
var f1 = isolate1.run(t1, null);
var f2 = isolate2.run(t2, null);
await Future.wait([f1, f2]);
await Future.wait([isolate1.close(), isolate2.close()]);
}
all you need is to add await, then you can store the values in a variable and you will get a simple list of answers.
void bigFunction() async {
final v1 = customWait(10);
final v2 = customWait(5);
final value = await Future.wait([v1, v2]);
print(value);
}
Future<int> customWait(int time) async {
await Future.delayed(Duration(seconds: time));
return time + 10;
}