Futures passed by reference have different hashcode, compare to false - flutter

I am having an hard time comparing Futures correctly in Dart. I hope I can be clear enough. I am doing two experiments:
This first test works. I create a future and then assign the same future to another variable. The Future is passed by reference so the variable is pointing at the same memory area and the objects are the same. Indeed the hashcode is the same and the comparison is true.
void main() {
Future f1 = Future.delayed(const Duration(seconds: 1));
Future f2 = f1;
print(f1.hashCode);
print(f2.hashCode);
print(f1 == f2);
}
Now, in this second test, I am doing the same operation but this time inside a method. I am trying to avoid to sending multiple calls to an API. I do one call and save the Future in a variable. When a method tries to send the request again, I pass him the pending Future instead of creating a new one.
In my mind, since the Futures are passed as a reference, they should still be the same and compare to true. Instead, I get false, and the Hashcode are completely different.
void main() {
Test();
}
class Test {
Future? _future = null;
Test() {
init();
}
void init() {
print("CALL 1");
Future d1 = processFuture();
print("ISNULL? "+(_future == null).toString());
print("CALL 2");
Future d2 = processFuture();
print("ISNULL? "+(_future == null).toString());
print(d1.hashCode);
print(d2.hashCode);
print(d1 == d2);
}
Future processFuture() async {
print("CALLED processFuture()");
if (_future != null){
print("FUTURE IS ALREADY THERE");
return _future;
}
print("NEW FUTURE REQUESTED");
_future = Future.delayed(const Duration(seconds: 1));
print("NEW FUTURE CREATED");
await _future;
print("FUTURE COMPLETE");
_future = null;
print("FUTURE IS NOW NULL");
return;
}
}
Am I missing something? Can someone explain me what I am misunderstanding and what am I doing wrong?
Thank you very much

Refactored the function to not be an async function, thanks #pskink
void main() {
Test();
}
class Test {
Future<dynamic>? _future = null;
Test() {
init();
}
void init() {
print("CALL 1");
Future<dynamic>? d1 = processFuture();
print("ISNULL? "+(_future == null).toString());
print("CALL 2");
Future<dynamic>? d2 = processFuture();
print("ISNULL? "+(_future == null).toString());
print(d1.hashCode);
print(d2.hashCode);
print(d1 == d2);
}
Future<dynamic>? processFuture() {
print("CALLED processFuture()");
if (_future != null){
print("FUTURE IS THERE");
return _future;
}
print("NEW FUTURE REQUESTED");
_future = Future.delayed(const Duration(seconds: 1))
.then((res) {
print("FUTURE COMPLETE");
_future = null;
print("FUTURE IS NOW NULL");
return res;
});
print("NEW FUTURE CREATED");
return _future;
}
}

A function marked with async implicitly returns new Future instances:
Future<int> foo() async => 42;
is implicitly equivalent to:
Future<int> foo() => Future.value(42);
If an async function internally returns a Future, then that Future is implicitly awaited, and its result is returned in a new Future. That is:
Future<int> bar() async => foo();
is implicitly equivalent to:
Future<int> bar() async => await foo();
so bar() returns a different Future than what foo() returned.

Related

How to pass a `where` clause to a function in Dart?

There is a function, getItems, and I would like to be able to have multiple where to modify the resulting list. I am new to Dart and cannot find the syntax for passing in a where.
I tried creating functions with custom where to call getItems, but cannot due to the async nature of getItems.
Future<List<IioMenuItem>> getItems() async {
// ...
final db = await openDatabase(path, readOnly: true);
final List<Map<String, dynamic>> maps = await db.query('menu_items');
final dbFilteredItems = maps.map((item) => IioMenuItem(
// assign values code removed
)).where((element) { // <-- make 'where' replaceable
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}).toList(growable: false);
return List.generate(dbFilteredItems.length, (i) {
return dbFilteredItems[i];
});
}
The failed attempt
Future<List<IioMenuItem>> menuItems(FilterState filterState) async {
final dbFilteredItems = getItems().where((element) { // The method 'where' isn't defined for the type 'Future'.
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}).toList(growable: false);
return List.generate(dbFilteredItems.length, (i) {
return dbFilteredItems[i];
});
}
Can I please get help?
The term you're looking for is a "closure" or "first class function".
See Functions as first-class objects on the Language guide.
"A where" isn't a thing. It's not a noun. Iterable.where is just the name of a function, and that function happens to take a function as a parameter, and uses it to determine what things to keep.
In this specific case, you want a function that takes a IioMenuItem, and returns a boolean that determins where or not to keep it. The type of that is a bool Function(IioMenuItem) (see Function).
I called it "predicate":
Future<List<IioMenuItem>> menuItems(
FilterState filterState,
bool Function(IioMenuItem) predicate // <- Take it in as a parameter
) async {
return (await getItems())
.where(predicate) // <- pass it along as an argument to `where`
.toList(growable: false);
}
You can pass any test inside a where.
filterItems(Map<String,dynamic> element) {
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}
final dbFilteredItems = maps.map((item) => IioMenuItem(
// assign values code removed
)).where(filterItems).toList(growable: false);
Use then in future
getItems().then((value) => value.where((element ...
Use await to call async functions.
Future<List<IioMenuItem>> menuItems(FilterState filterState) async {
final dbFilteredItems = (await getItems()).where((element) { // await has to be used here.
if (filterState == FilterState.all) {
return true;
} else {
return element.type.name == filterState.name;
}
}).toList(growable: false);
return dbFilteredItems;
}

How to make new "Future" instance based on function parameter in Dart?

In my code, "getResponse" is executed only once. How can I fix it?
I don't want to put "getResponse" inside "retry".
import "dart:math";
Future getResponse(int sec) async {
return Future.delayed(Duration(seconds: sec), () {
int rand = Random().nextInt(10);
print(rand);
if (rand < 5) {
return "success";
} else {
throw "rejected";
}
});
}
Future retry(Future f, [int count = 0]) async {
try {
return (await f);
} catch (e) {
if (count < 5) {
print(e);
retry(f, count + 1); // I think here is wrong.
}
}
}
void main() async => await retry(getResponse(1));
Function "retry" should execute getResponse until it successed
You cannot "retry" a future. Once it's done, it's done. You can however, create a new one every time, by passing a "future factory" (a function producing the relevant future) instead of the future:
import "dart:math";
Future getResponse(int sec) async {
return Future.delayed(Duration(seconds: sec), () {
int rand = Random().nextInt(10);
print(rand);
if (rand < 5) {
return "success";
} else {
throw "rejected";
}
});
}
// pass the factory function here
Future retry(Future Function() f, [int count = 0]) async {
try {
// call the function here to get a future to await
return (await f());
} catch (e) {
if (count < 5) {
print(e);
retry(f, count + 1);
}
}
}
// here, a function returning a future, instead of the future itself is passed
void main() async => await retry(() => getResponse(1));
The comment suggesting a loop instead of recursion is probably spot on too.

Flutter ensure I have a value in Async/Await and init functions [duplicate]

This question already has answers here:
What is a Future and how do I use it?
(6 answers)
Closed 20 days ago.
How can I make sure I have a state variable available after an async function call? My belief is because getValues() is async, it should "wait" until moving on to the next line. Thus, getValues() shouldn't exit and configValue() shouldn't be invoked until after my call to setState has finished. However the behavior I'm seeing it that values is an empty array in my Widget.
late List values = [];
#override
void initState() {
super.initState();
getValues();
configValue();
}
getValues() async {
final String response = await rootBundle.loadString('assets/values.json');
final vals = await json.decode(response)['values'];
setState(() {
values = vals;
});
}
void configValue() {
// How to make sure I have values[0] here?
}
Thanks in advance!
You can change your getValues to this:
Future<List> getValues() async {
final String response = await rootBundle.loadString('assets/values.json');
final vals = await json.decode(response)['values'];
return vals;
}
then create another middle function like this:
callasyncs() async {
var result = await getValues();
configValue(result);
}
and call it inside initState like this:
#override
void initState() {
super.initState();
callasyncs();
}
also change your configValue to this:
void configValue(List values) {
// now you have updated values here.
}
here your both configValue and getValues are separated from each other and also your configValue will wait for the getValues result.
you need to use await before the method to complete the future. also can be use .then.
Future<void> getVids() async { //I prefer retuning value
final String response = await rootBundle.loadString('assets/values.json');
final vals = await json.decode(response)['values'];
setState(() {
values = vals;
});
}
void configValue() async {
await getVids();
}
Try the following code:
List? values;
#override
void initState() {
super.initState();
getValues();
configValue();
}
Future<void> getVids() async {
final String response = await rootBundle.loadString('assets/values.json');
final vals = await json.decode(response)['values'];
setState(() {
values = vals;
});
}
void configValue() {
if (values != null) {
if (values!.isNotEmpty) {
…
}
}
}

type 'Future<List<Appointment>>' is not a subtype of type 'List<Appointment>' in type cast

The error should be clear but I'm unsure how to go around it.
Basically I have a Stream builder I'm calling every second by getData() method to update my SfCalendar with new data.
Stream<DataSource> getData() async* {
await Future.delayed(const Duration(seconds: 1)); //Mock delay
List<Appointment> appointments = foo() as List<Appointment>;
List<CalendarResource> resources = bar() as List<CalendarResource>;
DataSource data = DataSource(appointments, resources);
print("Fetched Data");
yield data;
}
But my appointments method foo() is of type Future<List> and not List.
Future<List<Appointment>> foo() async {
var url0 = Uri.https(
"uri",
"/profiles.json");
List<Appointment> appointments = [];
try {
final response = await dio.get(url0.toString());
//final Random random = Random();
//_colorCollection[random.nextInt(9)];
response.data.forEach((key, value) {
appointments.add(
Appointment(
id: int.parse(
value["id"],
),
startTime: DateTime.parse(value["startTime"]),
endTime: DateTime.parse(value["endTime"]),
),
);
});
} catch (error) {
print(error);
}
return appointments;
}
That is what the error should be telling, yes?
I tried removing the Future cast from foo() appointments but then I can't use async.
I also tried returning Future.value(appointments) but same error.
This is where I call my Stream in initState():
#override
void initState() {
super.initState();
print("Creating a sample stream...");
Stream<DataSource> stream = getData();
print("Created the stream");
stream.listen((data) {
print("DataReceived");
}, onDone: () {
print("Task Done");
}, onError: (error) {
print(error);
});
print("code controller is here");
}
Thank you, please help when possible
Just like JavaScript, async functions always return a Future. That's why you can't use async when you remove Future from the return type.
Since you're not waiting for that Future to resolve, you're actually trying to cast a Future to a List, which isn't a valid cast. All you should need to do is wait for the function to finish so it resolves to a List:
List<Appointment> appointments = await foo() as List<Appointment>;
and, since your return type is Future<List<Appointment>>, you don't actually need to cast the result.
List<Appointment> appointments = await foo();

is there any way to cancel a dart Future?

In a Dart UI, I have a button submit to launch a long async request. The submit handler returns a Future. Next, the button submit is replaced by a button cancel to allow the cancellation of the whole operation. In the cancel handler, I would like to cancel the long operation. How can I cancel the Future returned by the submit handler? I found no method to do that.
You can use CancelableOperation or CancelableCompleter to cancel a future. See below the 2 versions:
Solution 1: CancelableOperation (included in a test so you can try it yourself):
cancel a future
test("CancelableOperation with future", () async {
var cancellableOperation = CancelableOperation.fromFuture(
Future.value('future result'),
onCancel: () => {debugPrint('onCancel')},
);
// cancellableOperation.cancel(); // uncomment this to test cancellation
cancellableOperation.value.then((value) => {
debugPrint('then: $value'),
});
cancellableOperation.value.whenComplete(() => {
debugPrint('onDone'),
});
});
cancel a stream
test("CancelableOperation with stream", () async {
var cancellableOperation = CancelableOperation.fromFuture(
Future.value('future result'),
onCancel: () => {debugPrint('onCancel')},
);
// cancellableOperation.cancel(); // uncomment this to test cancellation
cancellableOperation.asStream().listen(
(value) => { debugPrint('value: $value') },
onDone: () => { debugPrint('onDone') },
);
});
Both above tests will output:
then: future result
onDone
Now if we uncomment the cancellableOperation.cancel(); then both above tests will output:
onCancel
Solution 2: CancelableCompleter (if you need more control)
test("CancelableCompleter is cancelled", () async {
CancelableCompleter completer = CancelableCompleter(onCancel: () {
print('onCancel');
});
// completer.operation.cancel(); // uncomment this to test cancellation
completer.complete(Future.value('future result'));
print('isCanceled: ${completer.isCanceled}');
print('isCompleted: ${completer.isCompleted}');
completer.operation.value.then((value) => {
print('then: $value'),
});
completer.operation.value.whenComplete(() => {
print('onDone'),
});
});
Output:
isCanceled: false
isCompleted: true
then: future result
onDone
Now if we uncomment the cancellableOperation.cancel(); we get output:
onCancel
isCanceled: true
isCompleted: true
Be aware that if you use await cancellableOperation.value or await completer.operation then the future will never return a result and it will await indefinitely if the operation was cancelled. This is because await cancellableOperation.value is the same as writing cancellableOperation.value.then(...) but then() will never be called if the operation was cancelled.
Remember to add async Dart package.
Code gist
How to cancel Future.delayed
A simple way is to use Timer instead :)
Timer _timer;
void _schedule() {
_timer = Timer(Duration(seconds: 2), () {
print('Do something after delay');
});
}
#override
void dispose() {
_timer?.cancel();
super.dispose();
}
As far as I know, there isn't a way to cancel a Future. But there is a way to cancel a Stream subscription, and maybe that can help you.
Calling onSubmit on a button returns a StreamSubscription object. You can explicitly store that object and then call cancel() on it to cancel the stream subscription:
StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {
// you code here
if (someCondition == true) {
subscription.cancel();
}
});
Later, as a response to some user action, perhaps, you can cancel the subscription:
For those, who are trying to achieve this in Flutter, here is the simple example for the same.
class MyPage extends StatelessWidget {
final CancelableCompleter<bool> _completer = CancelableCompleter(onCancel: () => false);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Future")),
body: Column(
children: <Widget>[
RaisedButton(
child: Text("Submit"),
onPressed: () async {
// it is true only if the future got completed
bool _isFutureCompleted = await _submit();
},
),
RaisedButton(child: Text("Cancel"), onPressed: _cancel),
],
),
);
}
Future<bool> _submit() async {
_completer.complete(Future.value(_solve()));
return _completer.operation.value;
}
// This is just a simple method that will finish the future in 5 seconds
Future<bool> _solve() async {
return await Future.delayed(Duration(seconds: 5), () => true);
}
void _cancel() async {
var value = await _completer.operation.cancel();
// if we stopped the future, we get false
assert(value == false);
}
}
One way I accomplished to 'cancel' a scheduled execution was using a Timer. In this case I was actually postponing it. :)
Timer _runJustOnceAtTheEnd;
void runMultipleTimes() {
_runJustOnceAtTheEnd?.cancel();
_runJustOnceAtTheEnd = null;
// do your processing
_runJustOnceAtTheEnd = Timer(Duration(seconds: 1), onceAtTheEndOfTheBatch);
}
void onceAtTheEndOfTheBatch() {
print("just once at the end of a batch!");
}
runMultipleTimes();
runMultipleTimes();
runMultipleTimes();
runMultipleTimes();
// will print 'just once at the end of a batch' one second after last execution
The runMultipleTimes() method will be called multiple times in sequence, but only after 1 second of a batch the onceAtTheEndOfTheBatch will be executed.
my 2 cents worth...
class CancelableFuture {
bool cancelled = false;
CancelableFuture(Duration duration, void Function() callback) {
Future<void>.delayed(duration, () {
if (!cancelled) {
callback();
}
});
}
void cancel() {
cancelled = true;
}
}
There is a CancelableOperation in the async package on pub.dev that you can use to do this now. This package is not to be confused with the built in dart core library dart:async, which doesn't have this class.
Change the future's task from 'do something' to 'do something unless it has been cancelled'. An obvious way to implement this would be to set a boolean flag and check it in the future's closure before embarking on processing, and perhaps at several points during the processing.
Also, this seems to be a bit of a hack, but setting the future's timeout to zero would appear to effectively cancel the future.
The following code helps to design the future function that timeouts and can be canceled manually.
import 'dart:async';
class API {
Completer<bool> _completer;
Timer _timer;
// This function returns 'true' only if timeout >= 5 and
// when cancelOperation() function is not called after this function call.
//
// Returns false otherwise
Future<bool> apiFunctionWithTimeout() async {
_completer = Completer<bool>();
// timeout > time taken to complete _timeConsumingOperation() (5 seconds)
const timeout = 6;
// timeout < time taken to complete _timeConsumingOperation() (5 seconds)
// const timeout = 4;
_timeConsumingOperation().then((response) {
if (_completer.isCompleted == false) {
_timer?.cancel();
_completer.complete(response);
}
});
_timer = Timer(Duration(seconds: timeout), () {
if (_completer.isCompleted == false) {
_completer.complete(false);
}
});
return _completer.future;
}
void cancelOperation() {
_timer?.cancel();
if (_completer.isCompleted == false) {
_completer.complete(false);
}
}
// this can be an HTTP call.
Future<bool> _timeConsumingOperation() async {
return await Future.delayed(Duration(seconds: 5), () => true);
}
}
void main() async {
API api = API();
api.apiFunctionWithTimeout().then((response) {
// prints 'true' if the function is not timed out or canceled, otherwise it prints false
print(response);
});
// manual cancellation. Uncomment the below line to cancel the operation.
//api.cancelOperation();
}
The return type can be changed from bool to your own data type. Completer object also should be changed accordingly.
A little class to unregister callbacks from future. This class will not prevent from execution, but can help when you need to switch to another future with the same type. Unfortunately I didn't test it, but:
class CancelableFuture<T> {
Function(Object) onErrorCallback;
Function(T) onSuccessCallback;
bool _wasCancelled = false;
CancelableFuture(Future<T> future,
{this.onSuccessCallback, this.onErrorCallback}) {
assert(onSuccessCallback != null || onErrorCallback != null);
future.then((value) {
if (!_wasCancelled && onSuccessCallback != null) {
onSuccessCallback(value);
}
}, onError: (e) {
if (!_wasCancelled && onErrorCallback != null) {
onErrorCallback(e);
}
});
}
cancel() {
_wasCancelled = true;
}
}
And here is example of usage. P.S. I use provider in my project:
_fetchPlannedLists() async {
if (_plannedListsResponse?.status != Status.LOADING) {
_plannedListsResponse = ApiResponse.loading();
notifyListeners();
}
_plannedListCancellable?.cancel();
_plannedListCancellable = CancelableFuture<List<PlannedList>>(
_plannedListRepository.fetchPlannedLists(),
onSuccessCallback: (plannedLists) {
_plannedListsResponse = ApiResponse.completed(plannedLists);
notifyListeners();
}, onErrorCallback: (e) {
print('Planned list provider error: $e');
_plannedListsResponse = ApiResponse.error(e);
notifyListeners();
});
}
You could use it in situations, when language changed, and request was made, you don't care about previous response and making another request!
In addition, I really was wondered that this feature didn't come from the box.
Here's a solution to cancel an awaitable delayed future
This solution is like an awaitable Timer or a cancelable Future.delayed: it's cancelable like a Timer AND awaitable like a Future.
It's base on a very simple class, CancelableCompleter, here's a demo:
import 'dart:async';
void main() async {
print('start');
// Create a completer that completes after 2 seconds…
final completer = CancelableCompleter.auto(Duration(seconds: 2));
// … but schedule the cancelation after 1 second
Future.delayed(Duration(seconds: 1), completer.cancel);
// We want to await the result
final result = await completer.future;
print(result ? 'completed' : 'canceled');
print('done');
// OUTPUT:
// start
// canceled
// done
}
Now the code of the class:
class CancelableCompleter {
CancelableCompleter.auto(Duration delay) : _completer = Completer() {
_timer = Timer(delay, _complete);
}
final Completer<bool> _completer;
late final Timer? _timer;
bool _isCompleted = false;
bool _isCanceled = false;
Future<bool> get future => _completer.future;
void cancel() {
if (!_isCompleted && !_isCanceled) {
_timer?.cancel();
_isCanceled = true;
_completer.complete(false);
}
}
void _complete() {
if (!_isCompleted && !_isCanceled) {
_isCompleted = true;
_completer.complete(true);
}
}
}
A running example with a more complete class is available in this DartPad.
You can use timeout() method
Create a dummy future:
Future<String?> _myFuture() async {
await Future.delayed(const Duration(seconds: 10));
return 'Future completed';
}
Setting a timeout of 3 seconds to stop early from 10sec:
_myFuture().timeout(
const Duration(seconds: 3),
onTimeout: () =>
'The process took too much time to finish. Please try again later',
);
and thats it you cancel your FUTURE.
there is no way unfortunately, take a look:
import 'dart:async';
import 'package:async/async.dart';
void main(List<String> args) async {
final object = SomeTimer();
await Future.delayed(Duration(seconds: 1));
object.dispose();
print('finish program');
}
class SomeTimer {
SomeTimer() {
init();
}
Future<void> init() async {
completer
.complete(Future.delayed(Duration(seconds: 10), () => someState = 1));
print('before wait');
await completer.operation.valueOrCancellation();
print('after wait');
if (completer.isCanceled) {
print('isCanceled');
return;
}
print('timer');
timer = Timer(Duration(seconds: 5), (() => print('finish timer')));
}
Timer? timer;
int _someState = 0;
set someState(int value) {
print('someState set to $value');
_someState = value;
}
CancelableCompleter completer = CancelableCompleter(onCancel: () {
print('onCancel');
});
void dispose() {
completer.operation.cancel();
timer?.cancel();
}
}
after ten seconds you will see someState set to 1 no matter what