I have this code :
widget.items.sort((a, b) {
await getItemDistance(a, true);
await getItemDistance(b, false);
return (itemADistance)
.compareTo(itemBDistance);
});
I am trying to sort widget.items list based on values returned from getItemDistance. However I get an error with a red squiggly line that says :
The await expression can only be used in an async function
and when I try to add async to the sort method I get another red squiggly line that says :
The argument type 'Future Function(Item, Item)' can't be assigned
to the parameter type 'int Function(Item, Item)'
How do I solve this dilemma guys ? :)
Thanks
List.sort is a synchronous function that expects a synchronous callback; there is no way to use it with an asynchronous callback. I would recommend transforming your List and doing any necessary asynchronous work first and then synchronously sorting the results. Doing so also should avoid calling getItemDistance (which, by virtue of being asynchronous, is likely to be expensive) on the same item multiple times. For example, something like:
final computedDistances = <Item, double>{};
for (final item in widget.items) {
final distance = await getItemDistance(item, ...);
computedDistances[item] = distance;
}
widget.items.sort((a, b) =>
computedDistances[a].compareTo(computedDistances[b])
);
(I don't know what the second argument to getItemDistance represents; if you can't get around that, you would need to build one Map with getItemDistance(..., true) results and one with getItemDistance(..., false) results1.)
As a last resort, you could write your own asynchronous sort function.
Edit #1
This should be a more efficient version since it doesn't wait for each asynchronous operation one-by-one:
final computedDistances = await Future.wait<double>([
for (final item in widget.items) getItemDistance(item, ...),
]);
final computedDistancesMap = <Item, double>{
for (var i = 0; i < widget.items.length; i += 1)
widget.items[i]: computedDistances[i],
};
widget.items.sort((a, b) =>
computedDistancesMap[a].compareTo(computedDistancesMap[b])
);
Edit #2
I've added a List.sortWithAsyncKey extension method to package:dartbag that can do this :
await widget.items.sortWithAsyncKey(
(element) => getItemDistance(element, ...),
);
1 However, if you need to call getItemDistance(..., true) for the first argument and getItemDistance(..., false) for the second argument, that implies that your comparison function probably is not self-consistent.
Whenever you are running async code you should add the async keyword like below. this tells dart you intend to run async code.
Async functions return a Future which tells dart that at some point in the future you are expecting itemADistance or an error.
You will want to return a Future of type int since you are expecting to receive an int itemADistance or an error.
Future<int> widget.items.sort((a, b) async {
await getItemDistance(a, true);
await getItemDistance(b, false);
return (itemADistance)
.compareTo(itemBDistance);
});
Related
I am using flutter with the cbl package to persist data. Trying to retrieve the entries does not seem to work because the function created is returning at the await statement and not the return statement. This does not seem like the intended result of darts async/await functionality. So I am lost.
task_database.dart
Future<dynamic> getAllTasks() async {
final tasksDb = await Database.openAsync(database); <---------- Returns here
var tasksQuery = const QueryBuilder()
.select(SelectResult.all())
.from(DataSource.database(tasksDb));
final resultSet = await tasksQuery.execute();
late var task;
await for (final result in resultSet.asStream()) {
final map = result.toPlainMap();
final taskDao = TaskDao.fromJson(map);
task = taskDao.task;
// Do something with the task...
print(task);
}
;
return task; <-------------------------------------------- Does not make it here
}
task_cubit.dart
getAllTasks() => {
allTaskMap = TasksAbcDatabase().getAllTasks(),
emit(TaskState(tasks: state. Tasks))
};
What I have tried. I have tried to use Database.openSync instead of Database.openAsync however, the function just returns at the next await statement. I have also tried making getAllTasks asynchronous and awaiting the database as such.
Future<void> getAllTasks() async => {
allTaskMap = await TasksAbcDatabase().getAllTasks(),
emit(TaskState(tasks: state. Tasks))
};
However this has the same issue, when the function from task_database returns prematurely it the returns at the first await function in getAllTasks which is the allTaskMap variable.
Thanks
A function cannot "return prematurely" without a return statement.
The only way the execution is cut short would be an exception being thrown.
I also don't see how you don't get syntax errors, when you don't await the Database.openAsync(database) statement.
So make sure all your awaits are in place. Use the linter to find those that are missing. While you are at it, remove the keyword dynamic from your vocabulary, it will only hurt you if you use it without a need for it. Your return type should be properly typed, then your compiler could tell you, that returning a single task from a function that is clearly supposed to return multiple tasks is not going to work.
Either catch your exceptions and make sure you know there was one, or do not catch them and watch them go all the way through into your debugger.
In addition, following the comment of #jamesdlin, your function definitions are... valid, but probably not doing what you think they are doing.
Future<void> getAllTasks() async => {
allTaskMap = await TasksAbcDatabase().getAllTasks(),
emit(TaskState(tasks: state. Tasks))
};
needs to be
Future<void> getAllTasks() async {
allTaskMap = await TasksAbcDatabase().getAllTasks();
emit(TaskState(tasks: state. Tasks));
}
I have a Dart Map() which I want to perform a .map() operation on to turn it into a second map. However, this conversion requires an await call. How might I do this in Dart? The below gives me an error of course, but how does one achieve this in one line?
Map secondMap = firstMap.map((key, value) async => MapEntry("key", await someAsyncFunction()))
Here is one way to do it:
final secondMap = Map.fromEntries(await Future.wait(firstMap.entries.map((entry) async => MapEntry(entry.key, await someAsyncFunction()))));
I am new to Flutter and the Dart programming language. I am making a shift-assignment app, but am stuck on this - I need to return a string from an async function which also waits for a value from other async functions. This is what I have written so far:
Future<String> getNumberOfShiftsText() async {
int weekShiftCount = 0;
List<DateTime> shiftTimes = [];
await getUserDetailsFromServer().then((value){
await getAppointmentsFromServer(cid, isEmployer, rolesList).then((aValue) {
List<HiveAppointment> appointmentList = appointmentBox.values.toList();
for (int i = 0; i < appointmentList.length; i++) {
if (appointmentList[i].startTime.isBefore(DateTime.now().add(const Duration(days: 7)))) {
weekShiftCount += 1;
shiftTimes.add(appointmentList[i].startTime);
}
}
});
});
return Future.delayed(const Duration(seconds: 1), () => '$weekShiftCount shift(s) assigned to you in the next 7 days',);
}
On running this, I get the error:
A non-null value must be returned since the return type 'String' doesn't allow null.
Future getNumberOfShiftsText() async {
Any ideas on how I can solve this? Any help would be much appreciated. Thanks in advance, and I apologize for my lack of knowledge.
I am not really sure about the core issue of your problem. But your code does contain a problematic pattern with mixing then() and await which can make your program run in an unexpected way.
I suggest rewrite your code into something like this:
Future<String> getNumberOfShiftsText() async {
int weekShiftCount = 0;
List<DateTime> shiftTimes = [];
final value = await getUserDetailsFromServer();
final aValue = await getAppointmentsFromServer(cid, isEmployer, rolesList);
List<HiveAppointment> appointmentList = appointmentBox.values.toList();
for (int i = 0; i < appointmentList.length; i++) {
if (appointmentList[i]
.startTime
.isBefore(DateTime.now().add(const Duration(days: 7)))) {
weekShiftCount += 1;
shiftTimes.add(appointmentList[i].startTime);
}
}
return '$weekShiftCount shift(s) assigned to you in the next 7 days';
}
I am not sure about your value variable since you are not using it in your code. Also the "wait 1 second before returning" has been removed since I think you added this because you did not know when the async code was done.
The rewritten code fixes this so when the return statement are executed, we know the other code are done being executed.
If you still have issues, can you provide a stacktrace combined with the full error so I know where the issue might be.
General suggestion
Avoid then whenever you can. Always prefer await.
Explanations
await makes so that dart stays at that line waiting for the function answer so you can do:
Future<int> func() async {
return 0;
}
int i = await func();
then also returns a Future which is the value you return inside its lambda or tearOff. But its idea is so that you can process asynchronous things inside a synchronous function. Means that whenever the future process gets done, it will start working on the then function, it will run "at the same time" (kinda) as the overlaying function (as I understand it, but you can always look for Decoding Flutter playlist on Youtube for trying to understand all this background processes better).
I have a piece of code that look like this:
final Either<Failure, Unit> resA = await deleteA();
resA.fold(
(failure) => handleFailure(),
(success) async {
final Either<Failure, Unit> resB = await deleteB();
resB.fold(
(failure) => handleFailure(),
(success) => handleSuccess(),
);
},
);
Basically, I want to call a first function, that returns either a Failure or a Unit (the success value doesn't matter).
Then, if the first function succeeded, I want to call another function, that also return either a Future or a Unit.
How could I do to avoid this ugly nested call of fold inside another fold?
I'm working with the package dartz, which is really cool, but lacks of documentation.
I am trying to get Contact using the function getContactsForPhone
getName()async{
number = '123-456-7890'
return await ContactsService.getContactsForPhone(number).then((value) => value.elementAt(0).displayName.toString());
}
but I am getting Future<dynmaic> instead of .displayName which is supposed to be String
You are mixing it up two way's to use Futures:
You can use await keyword to await for the conclusion.
You can use the then method to have a callback when the Future ends.
You need to choose one and stick with it. Using the await is always preferable because it makes the code more readable and avoids some callback hells.
In your case:
Future<String> getName() async {
number = '123-456-7890'
Iterable<Contact> myIterable = await ContactsService.getContactsForPhone(number);
List<Contact> myList = myIterable.toList();
return myList[0].displayName.toString()
}
Which should return the DisplayName you wanted.
Remember to also use the await keyword from the outside, wherever you call this function.
You can read more about Future and Asynchronous code here.