create async await steps with Timer instead of Future.delayed(...) - flutter

Is it possible to perform async steps with Timer instead of Future.delayed? The main reason is the availability of cancel() method in the Timer class, which allows me to cancel a running timer when a widget is disposed in Flutter.
import 'dart:async';
const kFirstStepDuration = Duration(seconds: 1);
const kSecondStepDuration = Duration(seconds: 1);
const kThirdStepDuration = Duration(seconds: 1);
void main() async{
await Future.delayed(kFirstStepDuration);
print('first step');
await Future.delayed(kSecondStepDuration);
print('second step');
await Future.delayed(kThirdStepDuration);
print('end');
}
Using the Timer following Richard Heap answer, I would like to cancel the steps whenever its possible, but the code below prompts:
first timer to begin
cancel first
cancel second
cancel third
My expectation was to prompt:
first timer to begin
cancel first
second timer to begin
cancel second
third timer to begin
cancel third
executed in 0:00:00.06000
import 'dart:async';
const kFirstStepDuration = Duration(seconds: 1);
const kSecondStepDuration = Duration(seconds: 1);
const kThirdStepDuration = Duration(seconds: 1);
Timer? firstTimer;
Timer? secondTimer;
Timer? thirdTimer;
final firstCompleter = Completer();
final secondCompleter = Completer();
final thirdCompleter = Completer();
void main() {
threadA();
threadB();
}
void threadA() async {
print('first timer to begin');
firstTimer = Timer(kFirstStepDuration, ()=> firstCompleter.complete());
await firstCompleter.future;
print('second timer to begin');
secondTimer = Timer(kSecondStepDuration, ()=> secondCompleter.complete());
await secondCompleter.future;
print('third timer to begin');
thirdTimer = Timer(kThirdStepDuration, ()=> thirdCompleter.complete());
await thirdCompleter.future;
}
void threadB() async {
await Future.delayed(Duration(milliseconds: 20));
firstTimer?.cancel();
print('cancel first');
await Future.delayed(Duration(milliseconds: 20));
secondTimer?.cancel();
print('cancel second');
await Future.delayed(Duration(milliseconds: 20));
thirdTimer?.cancel();
print('cancel third');
}

await a future that you complete in the timer's callback.
print('starting');
final completer = Completer();
final t = Timer(Duration(seconds: 3), () => completer.complete());
await completer.future;
print('done');
The problem with this is the completer won't complete if the timer is cancelled, as the callback is never called. So you have to cancel the timer and complete the completer.
Wouldn't it be easier to check the widget is still mounted after the Future.delayed?
EDIT
Following the update to your question, it seems you didn't the 'problem' above. You could encapsulate all the functionality into a class like:
class PointlessTimer {
PointlessTimer(Duration d) {
_timer = Timer(d, () => _completer.complete());
}
late final Timer _timer;
final _completer = Completer();
Future get future => _completer.future;
void cancel() {
_timer.cancel();
_completer.complete();
}
}
and use it like this:
void main() async {
print('starting');
final pointless = PointlessTimer(Duration(seconds: 3));
Timer(Duration(seconds: 2), () => pointless.cancel());
await pointless.future;
print('done');
}

Following Richard Heap answer:
/// works as a Future.delayed. Returns a timer to be canceled.
/// useful when disposing widgets before a timer completion.
/// necessary when testing widgets that disposes before the timer completion.
Future<Timer> _delayed(Duration duration) async {
final completer = Completer();
final timer = Timer(duration, () {
completer.complete();
});
await completer.future;
return timer;
}

Related

Flutter Future.delayed timer dispose when navigating to other page

In my flutter app, I have a function that has delaye for 5 seconds to activate a button. When I navigate to other page, the timer of the delayed is still working even though I use the "pushReplacement" navigator. Can anyone help me find a way to dispose or cancel this timer when I navigate to other page.
here is the code:
Future sendVerificationEmail() async {
try{
final user =FirebaseAuth.instance.currentUser!;
await user.sendEmailVerification();
setState(() => canResendEmail = false);
await Future.delayed(const Duration(seconds: 5)); // this is the line causing the error
setState(() => canResendEmail = true);
}catch (e) {
Utils.showSnackBar(e.toString());
}
}
and here is the navigation button function:
Future<void> SignOut() async {
await FirebaseAuth.instance.signOut();
Navigator.pushReplacement(
context,MaterialPageRoute(builder: (context) => mainPage()),
);
}
Try using a timer instead
Timer timer = Timer(Duration(seconds: 5), () {
//do something here();
});
// You can dispose the timer like this
timer.cancel();

`Stream.toList` stuck forever in a fake_async environment

void main() {
test('simple', () async {
await fakeAsync((async) async {
final stream = Stream.value(42);
print('hi before await');
await stream.toList();
print('hi after await');
});
});
}
stucks forever in the toList line...
I have also tried to call whatever methods, but still stuck forever in the "await" line
test('simple', () async {
await fakeAsync((async) async {
final stream = Stream.value(42);
final future = stream.toList();
print('hi before await');
async.flushMicrotasks();
async.flushTimers();
async.elapse(const Duration(seconds: 10));
async.elapseBlocking(const Duration(seconds: 10));
await future;
print('hi after await');
});
});
I have looked at the implementation of Stream.toList as follows, but it seems quite normal
Future<List<T>> toList() {
List<T> result = <T>[];
_Future<List<T>> future = new _Future<List<T>>();
this.listen(
(T data) {
result.add(data);
},
onError: future._completeError,
onDone: () {
future._complete(result);
},
cancelOnError: true);
return future;
}

How to pass data between isolates in flutter dart

I am buiding an app were I want to run a batch operation in firestore and I want to run it in a different isolate. Here is my code for spawning the isolate:
Future<void> _startAnotherIsolate(String mediaUrl) async {
final isolate = await FlutterIsolate.spawn(isolate1,"hello"); // i need to pass 2 more
arguments
Timer(Duration(seconds: 5), () {
print("Pausing Isolate 1");
isolate.pause();
});
Timer(Duration(seconds: 10), () {
print("Resuming Isolate 1");
isolate.resume();
});
Timer(Duration(seconds: 20), () {
print("Killing Isolate 1");
isolate.kill();
});
}
My code for the isolate:
void isolate1(String data1, String data2) async {
await Firebase.initializeApp();
print("changing profile picture: $phone");
Timer.periodic(Duration(seconds: 1), (timer) => print("Timer Running From Isolate 1"));
var db = FirebaseFirestore.instance;
var batch = db.batch();
FirebaseFirestore.instance.collection("posts").doc(phone).collection("userPosts")
.get().then((querySnapshot) {
for (var document in querySnapshot.docs) {
try {
batch.update(document.reference,{'user_image': mediaUrl});
} on FormatException catch (error) {
// If a document ID is unparsable. Example "lRt931gu83iukSSLwyei" is unparsable.
// print("The document ${error.source} could not be parsed.");
return null;
}
}
return batch.commit();
});
}
I have seen This link and this link but they are not helpful
import 'dart:isolate';
class RequiredArgs {
late final SendPort sendPort;
late int id;
RequiredArgs(this.id, this.sendPort);
}
Future<void> main() async {
ReceivePort receivePort = ReceivePort();
RequiredArgs requiredArgs = RequiredArgs(1122, receivePort.sendPort);
Isolate isolate = await Isolate.spawn(download, requiredArgs);
var resp = await receivePort.first;
print(resp);
}
void download(RequiredArgs requiredArgs) {
final SendPort sendPort = requiredArgs.sendPort;
final id = requiredArgs.id;
print(id);
sendPort.send("yes");
}
We pass the value using the RequiredArgs class. Hope my answer helps.

How can i get time of async function execution?

I need to do some actions if my http requests lasts more than 1 sec (But not stop it, so i can't use duration property)
How is this possible in Dart?
You can achieve this with a Timer from dart:async:
import 'dart:async';
Future<String> fetchData() async {
await Future.delayed(Duration(seconds: 5));
return 'data';
}
Future<void> main() async {
Timer timer;
timer = Timer(Duration(seconds: 1), () => print("Timer expired!"));
final data = await fetchData();
timer.cancel();
print(data);
}

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