How to make a delayed future cancelable in Dart? - flutter

Lets say that in Dart/Flutter you have the following code:
void myOperation() {
// could be anything
print('you didn't cancel me!');
}
Notice that the operation itself is not asynchronous and is void -- does not return anything.
We want it to execute at some point in the future, but we also want to be able to cancel it (because a new operation has been requested that supersedes it).
I've started by doing this:
Future.delayed(Duration(seconds: 2), myOperation())
... but this is not cancellable.
How exactly could you schedule that "operation," but also make it cancelable?
I'm thinking... we could modify the code like so:
Future.delayed(Duration(seconds: 2), () {
if (youStillWantThisToExecute) {
print('you didn't cancel me!');
}
});
But that's not really very good because it depends on a "global" boolean... and so if the boolean gets flipped to false, no operations will complete, even the most recently requested, which is the one we want to complete.
It would be nicer if there were a way to create any number of instances of the operation and cancel them on an individual basis... or to have a unique id assigned to each operation, and then instead of having a boolean control whether or not to execute... to have a "mostRecentId" int or something which is checked prior to execution.
Anyways...
CancelableOperation seemed promising just from its name.
So, I looked at its documentation:
CancelableOperation.fromFuture(Future inner, {FutureOr onCancel()})
Creates a CancelableOperation wrapping inner. [...] factory
But honestly that just makes my poor head hurt oh so much.
I've consulted other articles, questions, and answers, but they are all part of some specific (and complex) context and there isn't a dirt simple example anywhere to be found.
Is there a way to make a delayed future cancellable by wrapping it in some other class?
Can someone more experienced please provide at least one simple, complete, verified example that compiles in DartPad?
Thanks.

Use Timer:
var t = Timer(Duration(seconds: 400), () async {
client.close(force: true);
});
...
t.cancel();

Using CancalableOperation will not stop print('hello'); from executing even if you cancel. What it does is canceling(discarding) the result(void in your case). I will give you 2 examples using CancalableOperation and CancalableFuture.
CancelableOperation example
final delayedFuture = Future.delayed(
Duration(seconds: 2),
() {
return 'hello';
},
);
final cancellableOperation = CancelableOperation.fromFuture(
delayedFuture,
onCancel: () => {print('onCancel')},
);
cancellableOperation.value.then((value) => {
// Handle the future completion here
print('then: $value'),
});
cancellableOperation.value.whenComplete(() => {
print('onDone'),
});
cancellableOperation.cancel(); // <- commment this if you want to complete
CancelableFuture example
final delayedFuture = ...;
final cancalableFuture = CancelableFuture<String>(
future: delayedFuture,
onComplete: (result) {
// Use the result from the future to do stuff
print(result);
},
);
cancalableFuture.cancel(); // <- commment this if you want to complete
And the CancelableFuture implementation
class CancelableFuture<T> {
bool _cancelled = false;
CancelableFuture({
#required Future<dynamic> future,
#required void Function(T) onComplete,
}) {
future.then((value) {
if (!_cancelled) onComplete(value);
});
}
void cancel() {
_cancelled = true;
}
}

You cannot cancel an existing Future. If you do:
Future.delayed(
Duration(seconds: 2),
() {
print('hello');
},
);
as long as the process runs (and is processing its event queue) for at least 2 seconds, the Future eventually will execute and print 'hello'.
At best you can cause one of the Future's completion callbacks to fire prematurely so that callers can treat the operation as cancelled or failed, which is what CancelableOperation, et al. do.
Edit:
Based on your updated question, which now asks specifically about delayed Futures, you instead should consider using a Timer, which is cancelable. (However, unlike a Future, callers cannot directly wait on a Timer. If that matters to you, you would need to create a Completer, have callers wait on the Completer's Future, and let the Timer's callback complete it.)

Related

Flutter - Waiting for an asynchronous function call return from multiple synchronous function calls

I have an async function which is called multiple times synchoronusly.
List response = await Future.wait([future, future])
Inside, it popups a form and waiting for it to be submitted or cancelled.
var val = await Navigator.push(
context,
MaterialPageRoute(builder : (context) => const TheForm())
);
The first served Future will popup the form first and waiting for the return. No problem with that. But I want the second Future to check first if the form is already popped up. If it is, it just waiting for it to conclude and receive the same returned value.
I'm aware that receiving same function return from two calls sounds crazy and impossible. I'm just looking for a way to hold the second Future call on and trigger to conclude it from somewhere else.
Kindly tell me what I was missing and I'll provide the required information.
I try to use ValueNotifier's. Unfortunately ValueNotifier.addListener() only accept a VoidCallback. As for now, this is my solution. Still looking for a better way to replace the loop.
Future future() async{
if(ison) await Future.doWhile(() async {
await Future.delayed(Duration(seconds: 1));
return ison;
});
else{
ison = true;
result = ... //Popup form
ison = false;
}
return await result;
}
It sounds like you want to coalesce multiple calls to an asynchronous operation. Make your asynchronous operation cache the Future it returns and make subsequent calls return that Future directly. For example:
Future<Result>? _pending;
Future<Result> foo() {
if (_pending != null) {
return _pending!;
}
Future<Result> doActualWork() async {
// Stuff goes here (such as showing a form).
}
return _pending = doActualWork();
}
Now, no matter how many times you do await foo();, doActualWork() will be executed at most once.
If you instead want to allow doActualWork() to be executed multiple times and just to coalesce concurrent calls, then make doActualWork set _pending = null; immediately before it returns.

What is the purpose of awaiting the empty Future in this animation code?

I was going though GSkinner's flutter_vignattes codebase, in one of the functions there was an empty await for a Future
Future<void> _reset() async {
// Wait until next event loop to advance animation and call setState or flutter will yell at you
await Future<void>.value();
_controller.forward(from: 1.0 - _percentage * 0.83);
if (_isLoading) {
setState(() {
_model = BasketballGameModel.randomize();
});
}
_isLoading = false;
}
I understand how promises are sent to micro-task queue in JS (assuming same happens in Dart), but not quite able to understand the reason provided in the comment here i.e.,
// Wait until next event loop to advance animation and call setState or flutter will yell at you
Really appreciate if someone can provide a deeper insight into this. This is the particular line in codebase i am referring to.
https://github.com/gskinnerTeam/flutter_vignettes/blob/0ccc72c5b87b5ab6ba2dee9eff76f48ce2fadec8/vignettes/basketball_ptr/lib/demo.dart#L149
Future<void> function() {}
Defines an asynchronous function that ultimately returns nothing but can notify callers when it eventually completes. Also see: What's the difference between returning void vs returning Future?
Or You can learn from this https://github.com/dart-lang/sdk/issues/33415

Future<void> async vs simply void async in Dart

In Dart,
what is the difference between saying
Future<void> doStuff() async { ...
and
void doStuff() async { ...
I know what a Future<T> is and how async/await work generally, but I never realized Future<void> was a thing. I have some code that has the Future<void> all over the place and I want to replace it with my normal way of doing things, but I don't want to break anything.
Notice that both functions use async. The question is NOT 'what is the difference between async and non-async functions?' or 'can you give a brief attempt at explaining asynchronous programming in Dart, please?'
I'm aware that there is a pretty much identical question already, but if you look closely at the answers you will see nobody actually answered the question in a clear way -- what is the difference? Is there any difference? Is there no difference?
To elaborate, consider the following two functions:
// notice there is no warning about not returning anything
Future<void> futureVoid() async {
await Future.delayed(Duration(seconds: 2), () {
var time = DateTime.now().toString();
print('$time : delay elapsed');
});
}
void nonFutureVoid() async {
await Future.delayed(Duration(seconds: 2), () {
var time = DateTime.now().toString();
print('$time : delay elapsed');
});
}
Then test them with a button whose onPressed() function is:
onPressed: () async {
await nonFutureVoid(); // notce that this await *DOES* delay execution of the proceeding lines.
var time = DateTime.now().toString();
print('$time : <-- executed after await statement');
}
Log result:
flutter: 2021-02-23 21:46:07.436496 : delay elapsed
flutter: 2021-02-23 21:46:07.437278 : <-- executed after await statement
As you can see, they both behave exactly the same way -- the simple void async version IS awaited. So what is the difference?
With your edit, your question makes more sense. Your question is really about principle vs. practice.
In principle, a function that returns void is different from a function that returns Future<void>. One conceptually represents something that does not return anything, and the other represents an asynchronous computation that can fire callbacks when it completes.
In principle, you should never attempt to use the value returned from a void function. It doesn't make sense. If you run the Dart analyzer against code that does await nonFutureVoid(); you'll get a warning if the await_only_futures lint is enabled.
In practice, there are cases where attempting to use a void return value happens to not generate an error. Those are quirks in the language (or bugs in the implementation); you should not rely on them. (Dart didn't originally have a void return type. When it was added later, it wasn't implemented to mean "no value" but instead to mean "a value that you aren't allowed to use". See Dart 2: Legacy of the void. Normally that subtle difference shouldn't matter.)
Being able to do await nonFutureVoid(); is a bug1, and it seems that that bug is now fixed: await nonFutureVoid(); is an error if you use Dart 2.12 or later and enable null-safety and the stricter type-checking that comes with it.
You can observe the old and new behaviors with DartPad by toggling the "Null Safety" button: https://dartpad.dartlang.org/b0dae0d5a50e302d26d93f2db3fa6207
1 There are a lot of issues filed on GitHub with a lot of back-and-forth discussion, so yes, it is rather confusing. Most people seemed to agree that allowing await void was undesirable, however.
A void function indicates that the function returns nothing, which means you can not act on the result of that Function (and cannot await on it's result).
Meanwhile, the Future<void> function returns a Future object (that has value of void). If you don't have a return statement, a missing_return warning will show up (it can still be compiled). You can still act on that result by awaiting it, but cannot actually use the value because it's void.
While it seems like it'd be just fine with whatever you are using, I think it's better to use Future for every async function for type-safety and better maintenance.

What is a Future and how do I use it?

I get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
It might be another type instead of int, but basically the pattern is:
A value of type 'Future<T>' can't be assigned to a variable of type 'T'
So:
What exactly is a Future?
How do I get the actual value I want to get?
What widget do I use to display my value when all I have is a Future<T>?
In case you are familiar with Task<T> or Promise<T> and the async/ await pattern, then you can skip right to the "How to use a Future with the widgets in Flutter" section.
What is a Future and how do I use it?
Well, the documentation says:
An object representing a delayed computation.
That is correct. It's also a little abstract and dry. Normally, a function returns a result. Sequentially. The function is called, runs and returns it's result. Until then, the caller waits. Some functions, especially when they access resources like hardware or network, take a little time to do so. Imagine an avatar picture being loaded from a web server, a user's data being loaded from a database or just the texts of the app in multiple languages being loaded from device memory. That might be slow.
Most applications by default have a single flow of control. When this flow is blocked, for example by waiting for a computation or resource access that takes time, the application just freezes. You may remember this as standard if you are old enough, but in today's world that would be seen as a bug. Even if something takes time, we get a little animation. A spinner, an hourglass, maybe a progress bar. But how can an application run and show an animation and yet still wait for the result? The answer is: asynchronous operations. Operations that still run while your code waits for something. Now how does the compiler know, whether it should actually stop everything and wait for a result or continue with all the background work and wait only in this instance? Well, it cannot figure that out on it's own. We have to tell it.
This is achieved through a pattern known as async and await. It's not specific to flutter or dart, it exists under the same name in many other languages. You can find the documentation for Dart here.
Since a method that takes some time cannot return immediately, it will return the promise of delivering a value when it's done.
That is called a Future. So the promise to load a number from the database would return a Future<int> while the promise to return a list of movies from an internet search might return a Future<List<Movie>>. A Future<T> is something that in the future will give you a T.
Lets try a different explanation:
A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.
Most likely, as you aren't doing this just for fun, you actually need the results of that Future<T> to progress in your application. You need to display the number from the database or the list of movies found. So you want to wait, until the result is there. This is where await comes in:
Future<List<Movie>> result = loadMoviesFromSearch(input);
// right here, you need the result. So you wait for it:
List<Movie> movies = await result;
But wait, haven't we come full circle? Aren't we waiting on the result again? Yes, indeed we are. Programs would be utterly chaotic if they did not have some resemblence of sequential flow. But the point is that using the keyword await we have told the compiler, that at this point, while we want to wait for the result, we do not want our application to just freeze. We want all the other running operations like for example animations to continue.
However, you can only use the await keyword in functions that themselves are marked as async and return a Future<T>. Because when you await something, then the function that is awaiting can no longer return their result immediately. You can only return what you have, if you have to wait for it, you have to return a promise to deliver it later.
Future<Pizza> getPizza() async {
Future<PizzaBox> delivery = orderPizza();
var pizzaBox = await delivery;
var pizza = pizzaBox.unwrap();
return pizza;
}
Our getPizza function has to wait for the pizza, so instead of returning Pizza immediately, it has to return the promise that a pizza will be there in the future. Now you can, in turn, await the getPizza function somewhere.
How to use a Future with the widgets in Flutter?
All the widgets in flutter expect real values. Not some promise of a value to come at a later time. When a button needs a text, it cannot use a promise that text will come later. It needs to display the button now, so it needs the text now.
But sometimes, all you have is a Future<T>. That is where FutureBuilder comes in. You can use it when you have a future, to display one thing while you are waiting for it (for example a progress indicator) and another thing when it's done (for example the result).
Let's take a look at our pizza example. You want to order pizza, you want a progress indicator while you wait for it, you want to see the result once it's delivered, and maybe show an error message when there is an error:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
/// ordering a pizza takes 5 seconds
/// and then gives you a pizza salami with extra cheese
Future<String> orderPizza() {
return Future<String>.delayed(
const Duration(seconds: 5),
() async => 'Pizza Salami, Extra Cheese');
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
body: Center(
child: PizzaOrder(),
),
),
);
}
}
class PizzaOrder extends StatefulWidget {
#override
_PizzaOrderState createState() => _PizzaOrderState();
}
class _PizzaOrderState extends State<PizzaOrder> {
Future<String>? delivery;
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: delivery != null
? null
: () => setState(() {
delivery = orderPizza();
}),
child: const Text('Order Pizza Now')
),
delivery == null
? const Text('No delivery scheduled')
: FutureBuilder(
future: delivery,
builder: (context, snapshot) {
if(snapshot.hasData) {
return Text('Delivery done: ${snapshot.data}');
} else if(snapshot.hasError) {
return Text('Delivery error: ${snapshot.error.toString()}');
} else {
return const CircularProgressIndicator();
}
})
]);
}
}
This is how you use a FutureBuilder to display the result of your future once you have it.
Here's a list of analogies to Dart's Future from other languages:
JS: Promise
Java: Future
Python: Future
C#: Task
Just like in other languages Future is a special type of object which allows to use async/await syntax sugar, write asynchronous code in synchronous/linear way. You return Future from an async method rather than accept a callback as a parameter and avoid the callback hell - both Futures and callbacks solve same problems (firing some code at a latter time) but in a different way.
Future<T> returning the potential value which will be done by async work
Eg:
Future<int> getValue() async {
return Future.value(5);
}
Above code is returning Future.value(5) which is of int type, but while receiving the value from method we can't use type Future<int> i.e
Future<int> value = await getValue(); // Not Allowed
// Error
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
To solve above getValue() should be received under int type
int value = await getValue(); // right way as it returning the potential value.
I hope this key point will be informative, I show it in two different Async methods:
Note the following method where showLoading(), getAllCarsFromApi() and hideLoading() are inner Async methods.
If I put the await keyword before showLoading(), the Operation waits until it's done then goes to the next line but I intentionally removed the await because I need my Loading dialog be displayed simultaneously with getAllCarsFromApi() is being processed, so it means showLoading() and getAllCarsFromApi() methods are processed on different Threads. Finally hideLoading() hides the loading dialog.
Future<List<Car>> getData() async{
showLoading();
final List<Car> cars = await getAllCarsFromApi();
hideLoading();
return cars;
}
Now look at this another Async method, here the getCarByIdFromApi() method needs an id which is calculated from the getCarIdFromDatabase(), so there must be an await keyword before the first method to make the Operation wait until id is calculated and passed to the second method. So here two methods are processed one after another and in a single Thread.
Future<Car> getCar() async{
int id = await getCarIdFromDatabase();
final Car car = await getCarByIdFromApi(id);
return car;
}
A simple answer is that if a function returns its value with a delay of some time, Future is used to get its value.
Future<int> calculate({required int val1, required int val2}) async {
await Future.delayed(const Duration(seconds: 2));
return val1 + val2;
}
if we call the above function as
getTotal() async {
int result = calculate(val1: 5, val2: 5);
print(result);
}
we will get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
but if we use await before function call it will give the actual returned value from the function after a delay
getTotal() async {
int result = await calculate(val1: 5, val2: 5);
print(result);
}
the keyword async is required to use await for the Future to get returned value
I am trying to give very simple example. Suppose you have ordered something online, let it be a shirt. then you have to wait until the order is dispatched and delivered to your home. In the meanwhile you will not stop working your daily activities/work anything you do and after a day if it delivered to your home you will collect it and wear it. Now, look at the following example.
Ok, now let's make a function which handles our order delivery.(Read Comments Also)
//order function which will book our order and return our order(which is our shirt). don't focus on Order object type just focus on how this function work and you will get to know about future definitely.
Future<Order> orderSomething(){
//here our order processing and it will return our order after 24 hrs :)
await Future.delayed(const Duration(hours: 24),() => Order('data'));
}
Now
void main() {
//now here you have called orderSomething() and you dont want to wait for it to be delivered
//you are not dependent on your order to do your other activities
// so when your order arrives you will get to know
orderSomething()
wearSomething()
goingCollege()
}
Now if you are dependent on your order then you have to add await async ( i will show you where)
void main() async{
//now you're dependent on your order you want to wait for your order
await orderSomething()
wearOrderedShirt() // :)
goingCollege()
}
Now most of the times in flutter applications you will have to await for your API calls(Network), for background task for downloading/uploading, for database calls etc.

Dart's Future Interface (doWhile, microtask and sync)

Haven't seen a lot of info about it online...
What are the possible use cases in which I'd want to use, either Future.doWhile, Future.microtask or Future.sync?
Future.sync
A lot of times after a button press for example, I'd want a Future to happen immediately,
but I wouldn't want it to block the UI, is that a good use case for Future.sync or is that better to use Future and let dart handle when thing will get executed?
I'd want a Future to happen immediately...
You can't make Future to happen immediately because it needs some time to be executed. Instead you can block UI thread while future is executing. The pseudo code looks like that:
T runSync<T>(Future<T> future) {
while (future.running) sleep(10);
return future.result;
}
This will block your ui thread. That's why we are using Futures. Futures used for specific tasks that's not resolved immediately (usually I/O tasks, eg: network requests, file read/write) to get notified when future resolves without blocking UI thread.
Here's how I'm handling futures without blocking UI thread:
class MyState extends State<..> {
bool _running = false;
Future<String> doTask() async {
// some long running IO tasks
return 'Hello world';
}
Future handlePress() async {
setState(() { _running = true; });
try {
await doTask();
} finally {
if (mounted) {
setState(() { _running = false; });
}
}
}
Widget build(BuildContext context) {
return FlatButton(
child: Text('Execute'),
// Disable button if task is currently running to block parallel calls (for example sending same http request twice)
onPressed: _running ? null : handlePress,
);
}
}
In this code when user presses FlatButton I'm setting _running to true to disable FlatButton until Future is running.