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.
Related
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.
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
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.)
I am using BLoC in flutter.
As soon as BLoC instance is created I want to make to API calls. To achieve that, I have added the following code inside the constructor.
class MyBloc extends Bloc<MyBlocEvent, MyBlocState> {
MyBloc() {
_repository = MyAccountRepository();
_myAccountList = List();
add(API1CallEevent());
add(API2CallEevent());
}
...
and the event handling part
...
#override
Stream<MyBlocState> mapEventToState(MyBlocEvent event) async* {
if (event is API1CallEevent) {
var ap1 =
await _repository.getAPI1();
----
----
}else if (event is API2CallEevent) {
var api2 =
await _repository.getAPI2();
----
---
}
}
The problem I am facing is that the API calls are not executed parallel, which means after API1CallEvent is completed then API2CallEvent get executed...
is there any way to do that in parallel?
In my opinion, doing two API calls in parallel and expecting result at the same time is not much related to BLoC.
It is better if each bloc-event triggers a specific set of actions, and events are decoupled from each other.
Additionally;
Instead of raising an event inside init block, it is better to do that when you init Bloc inside a provider. See example;
BlocProvider<AuthBloc>(
lazy: false,
create: (context) => AuthBloc(
userRepository: _userRepository,
)..add(AppStartedEvent()),
),
This generates an event right after Bloc is initialized.
A bloc basically is a state machine. It does not do parallelism, that's not what it's built for. It's sequentially going from one state into another. In doing that, it can do things in parallel internally, but it cannot (or should not) take input in parallel.
If you want one event to execute multiple awaitable actions in parallel, you can do that:
#override
Stream<MyBlocState> mapEventToState(MyBlocEvent event) async* {
if (event is CallTheAPIsEvent) {
final results = await Future.wait([
_repository.getAPI1(),
_repository.getAPI2()
]);
// do something with the results
yield ApisHaveBeenCalledState();
}
// more event handling
}
I'm trying out Flutter, but I'm having trouble getting the UI to update consistently. I'd like to show a status message while a long-running async method is called, but the setState() call I make just before calling the long-running method doesn't seem to cause my build() method to get invoked.
I've created a simple example that calculates the Fibonacci number for a randomly selected number between 25 and 30. In my sample code/app, hitting the "calc" button calls _calc(). _calc() picks a random number, sets a status message "Calculating Fib of $num..." tied to a text widget (_status) and updates it with setState(); then calls the async _fib() routine to calculate the number; then updates _status with the result using setState(). Additionally, the build() method prints the value of _status to the console, which can be used to see when build() is invoked.
In practice, when the button is pressed, the first status message does not appear either in the debug console, or on the UI. Doing a bit of experimentation, I added a pseudo sleep function that I call just prior to calling _fib(). This sometimes causes the first setState() call to work properly - invoking build(). The longer I make the sleep, the more often it works. (I'm using values from a few milliseconds up to a full second).
So my question are: What am I doing wrong? and What's the right way to do this? Using the pseudo sleep is obviously not the correct solution.
Other, probably not too relevant info: My dev environment is Android Studio 3.1.2 on a Win10 machine. Using Android SDK 27.0.3, with Flutter beta 0.3.2. My target device is the emulator for a pixel2 running Android 8.1. Also, sorry if my lack of 'new' keywords is off-putting, but from what I read in Dart 2 release notes, it's not usually necessary now.
import 'package:flutter/material.dart';
import "dart:async";
import "dart:math";
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Debug Toy',
home: MyWidget(),
);
}
}
class MyWidget extends StatefulWidget {
#override
MyWidgetState createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
String _status = "Initialized";
final rand = Random();
Future sleep1() async {
return new Future.delayed(const Duration(milliseconds: 100),() => "1");
}
Future<Null> _resetState() async {
setState(() { _status = "State Reset"; });
}
Future<Null> _calc() async {
// calculate something that takes a while
int num = 25 + rand.nextInt(5);
setState(() { _status = "Calculating Fib of $num..."; });
//await sleep1(); // without this, the status above does not appear
int fn = await _fib(num);
// update the display
setState(() { _status = "Fib($num) = $fn"; });
}
Future<int> _fib(int n) async {
if (n<=0) return 0;
if ((n==1) || (n==2)) return 1;
return await _fib(n-1) + await _fib(n-2);
}
#override
Widget build(BuildContext context) {
print("Build called with status: $_status");
return Scaffold(
appBar: AppBar(title: Text('Flutter Debug Toy')),
body: Column(
children: <Widget>[
Container(
child: Row(children: <Widget>[
RaisedButton( child: Text("Reset"), onPressed: _resetState, ),
RaisedButton( child: Text("Calc"), onPressed: _calc, )
]),
),
Text(_status),
],
),
);
}
}
Let's start by going to one extreme and rewriting fib as fibSync
int fibSync(int n) {
if (n <= 0) return 0;
if (n == 1 || n == 2) return 1;
return fibSync(n - 1) + fibSync(n - 2);
}
and calling that
Future<Null> _calc() async {
// calculate something that takes a while
int num = 25 + rand.nextInt(5);
setState(() {
_status = "Calculating Fib of $num...";
});
//await Future.delayed(Duration(milliseconds: 100));
int fn = fibSync(num);
// update the display
setState(() {
_status = "Fib($num) = $fn";
});
}
The first setState just marks the Widget as needing to be rebuilt and (without the 'sleep') continues straight into the calculation, never giving the framework the chance to rebuild the Widget, so the 'Calculating' message isn't displayed. The second setState is called after the calculation and once again (redundantly) marks the Widget as needing to be rebuilt.
So, the execution order is:
Set status to Calculating, mark Widget as dirty
Perform the synchronous calculation
Set status to Result, mark Widget as dirty (redundantly)
Framework finally gets chance to rebuild; build method is called
When we uncomment the 'sleep', the execution order changes to
Set status to Calculating, mark Widget as dirty
'Sleep', allowing the framework to call build
Perform the synchronous calculation
Set status to Result, mark Widget as dirty (again)
Framework calls build
(As an aside, note how the synchronous fib calculation is an order of magnitude faster because it doesn't have to do all the microtask scheduling.)
Let's re-consider the async calculation. What's the motivation of making it async? So that the UI remains responsive during the calculation? As you've seen, that doesn't achieve the desired effect. You still only have one thread of execution, and you aren't allowing any gaps in execution for callbacks and rendering to occur. Sleeping for 100ms is not compute bound, so drawing etc can occur.
We use async functions to wait for external events, like replies from web servers, where we don't have anything to do until the reply arrives, and we can use that time to keep rendering the display, reacting to gestures, etc.
For compute bound stuff, you need a second thread of execution which is achieved with an Isolate. An isolate has its own heap, so you have to pass it its data, it works away in its own space, then passes back some results. You can also stop it, if it's taking too long, or the user cancels, etc.
(There are much less computationally expensive ways to calculate fibs, but I guess we're using the recursive version as a good example of an O(n^2) function, right?)