Flutter - How to delay a function for some seconds - flutter

I have a function which returns false when the user select incorrect answer.
tappedbutton(int index) async {
final userAnswer = await userAnswer();
if (userAnswer) {
// Executing some code
}else{
ErrorSnackbar(); // This snackbar takes 2 second to close.
}
My objective is to delay calling the function for two seconds(user can click the button again , with no action triggering) after the user selects the wrong answer and to prevent the click immediatly. How can i achieve it?

You'll have to add an helper variable in the outer scope, that will indicate whether the user is on an answer cooldown or not.
The shortest solution will be:
var answerCooldownInProgress = false;
tappedbutton(int index) async {
// Ignore user taps when cooldown is ongoing
if (answerCooldownInProgress) {
return;
}
final userAnswer = await userAnswer();
if (userAnswer) {
// ...
} else {
ErrorSnackbar();
answerCooldownInProgress = true;
await Future.delayed(const Duration(seconds: 2));
answerCooldownInProgress = false;
}
}

You can use Future.delay or Timer() class to achieve that.

In order to delay a function you can do below code or use Timer() class
tappedbutton(int index) async {
await Future.delayed(Duration(seconds: 2));
}

I don't think that your goal is to delay the function.
You're trying to find a way to let the user wait until the ErrorSnackbar is gone, right?
Try this approach. It saves the time when a button was clicked the last time and cancels every button press until 2 seconds have passed.
DateTime lastPressed = DateTime(0);
tappedButton(int index) async {
DateTime now = DateTime.now();
if (lastPressed.difference(now).inSeconds < 2) {
// button pressed again in under 2 seconds, cancel action
return;
}
lastPressed = now;
final userAnswer = await userAnswer();
if (userAnswer) {
// answer correct
} else{
// answer incorrect
ErrorSnackbar();
}
}

Related

How to conditionally click an element in Protractor?

In a protractor test, I need to close a pop-up if it appears (it doesn't always) and proceed with the test as normal. Here's the code I've got to do so-
let checkForPopUp = async function() {
element(by.css('button[id="gdprStopEmails"]')).isPresent().then(function (isVisible) {
return isVisible;
});
}
it('description', async function() {
let hasPopUp = await checkForPopUp();
if(hasPopUp) {
await element(by.id("gdprStopEmails")).click();
}
await connectedAccounts.revokePermission(partnerInfo.revokeId, partnerInfo.confirmRevokeId);
});
I ran this test a few times without checking if the element was there, and it closed the popup every time it was there (and failed the test when it wasn't). It hasn't closed the popup a single time since I introduced the condition check, and despite my best efforts I can't figure out what's up. Does anything jump out to you guys? Thanks in advance!
you're missing return. Your function returns nothing explicitly and thus implicitly returns undefined which is always falsy, and your if block doesn't get executed
This should work
let checkForPopUp = async function() {
return element(by.css('button[id="gdprStopEmails"]')).isPresent().then(function (isVisible) {
return isVisible;
});
}
it('description', async function() {
let hasPopUp = await checkForPopUp();
if(hasPopUp) {
await element(by.id("gdprStopEmails")).click();
}
await connectedAccounts.revokePermission(partnerInfo.revokeId, partnerInfo.confirmRevokeId);
});
but since you're using async/await don't mix syntaxes. I'd do it this way
let checkForPopUp = async function() {
return element(by.css('button[id="gdprStopEmails"]')).isPresent()
}
it('description', async function() {
let hasPopUp = await checkForPopUp();
if(hasPopUp) {
await element(by.id("gdprStopEmails")).click();
}
await connectedAccounts.revokePermission(partnerInfo.revokeId, partnerInfo.confirmRevokeId);
});
and keep in mind isPresent doesn't guarantee the visibility

Flutter/Dart: can you run a background service with Future.delayed?

I have to run a bunch of tasks every day on a Dart/Flutter project. This is how I currently do it:
class TaskScheduler {
DateTime _lastUpdate;
bool _isRunning = false;
void launchDailyTasks() async {
//make sure tasks are not already scheduled
if (_isRunning) return;
//check last updates
if (_lastUpdate == null) {
SharedPreferences prefs = await SharedPreferences.getInstance();
final _stamp = prefs.getInt(prefsKey(AppConstants.LAST_SYNC));
if (_stamp != null) {
_lastUpdate = DateTime.fromMillisecondsSinceEpoch(_stamp);
} else {
_lastUpdate = DateTime.now();
}
}
if (_lastUpdate.isBefore(DateTime.now().add(Duration(days: 1)))) {
_runWorkersLoop();
} else {
final _delay =
DateTime.now().difference(_lastUpdate.add(Duration(days: 1)));
Timer(_delay, () => _runWorkersLoop());
}
}
void _runWorkersLoop() async {
_isRunning = true;
_startDailyTasks();
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt(prefsKey(AppConstants.LAST_SYNC),
DateTime.now().millisecondsSinceEpoch);
_lastUpdate = DateTime.now();
Future.delayed(Duration(days: 1), () => _runWorkersLoop());
}
}
And so I've been wondering: is this wrong? Why should I use a package like https://pub.dev/packages/cron to do this if this works?
In reviewing your example, you use Timer() initially, and then _runWorkersLoop() implements it's own "periodic" loop by calling itself with Future.delayed(). One way to maybe simplify this is to use Timer.periodic() which you call once and until you cancel it, it will repeat.
https://api.dart.dev/stable/2.12.0/dart-async/Timer/Timer.periodic.html
Then you have a timer instance that you can check if it's running with isRunning() and you can cancel at any time with cancel().
I looked at the source for cron lib and it uses Future.microtask which is similar to Future.delayed. Generally using a lib like that will help give you:
more eyes on the code to fix any bugs versus a home grown solution
more general functionality that you might want to use later
easier to learn/understand for someone picking up your code through more examples of use available
I assume you don't have any critical timing requirements down to the millisecond with when your stuff runs, so I think you might be interested in looking at a periodic timer as mentioned above.
One thing you might want to protect is that if a bug later calls your _runWorkersLoop() function when it's already running, it will call Future.delayed() again even though one already is waiting. You don't have a way to check for existing Future.delayed() instances but with Timer.peridic() you can use the "isRunning()" to check.
Here is the improved version after #Eradicatore's comment, in case someone is interested. Works as promised.
class TaskScheduler {
Timer _timer;
/// Launch the daily tasks.
/// If [forceUpdate] is true, any currently running task will be canceled and rerun.
/// Otherwise a new task will only be started if no previous job is running.
void launchDailyTasks({bool forceUpdate = false}) async {
bool checkLastSync = true;
if (forceUpdate) {
//cancel
if (_timer != null) _timer.cancel();
checkLastSync = false;
} else {
//don't start tasks if a previous job is running
if (_timer != null && _timer.isActive) {
return;
}
}
Duration startDelay = Duration();
if (checkLastSync) {
//check last sync date to determine when to start the timer
SharedPreferences prefs = await SharedPreferences.getInstance();
int timestamp = prefs.getInt(prefsKey(AppConstants.LAST_SYNC));
if (timestamp == null) timestamp = DateTime.now().millisecondsSinceEpoch;
final difference = DateTime.now()
.difference(DateTime.fromMillisecondsSinceEpoch(timestamp));
if (difference.inDays < 1) {
//start the timer when 1 day is reached
startDelay = Duration(
seconds: Duration(days: 1).inSeconds - difference.inSeconds);
}
}
//start tasks
Future.delayed(startDelay, () {
//run first tasks immediately once
_runWorkersLoop();
//setup periodic after
_timer = Timer.periodic(Duration(days: 1), (Timer t) {
_runWorkersLoop();
});
});
}
void _runWorkersLoop() async {
_startDailyTasks();
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt(prefsKey(AppConstants.LAST_SYNC),
DateTime.now().millisecondsSinceEpoch);
}
}

Return code results after some delay in Flutter?

I'm trying to run this Future function that runs a Timer once 500 milliseconds have passed. The issue I'm having is that the timer is not passing the data to the results variable so the _destinationOnSearchChanged() ends up returning null.
Note: getCity(context, _typeAheadController.text); does return data but only inside the Timer function.
Future _destinationOnSearchChanged() async {
dynamic results;
//Cancels timer if its still running
if (_apiCityThrottle?.isActive ?? false) {
_apiCityThrottle.cancel();
}
//Makes API call half a second after last typed button
_apiCityThrottle = Timer(const Duration(milliseconds: 500), () async{
results = await getCity(context, _typeAheadController.text);
});
print(results);
return await results;
}
As pskink noted in a comment, you probably should look into existing debounce mechanisms instead of creating your own.
If you still want to proceed down this path: your problem is that create a Timer and then return results immediately. You don't wait for the Timer to fire (and you can't directly await a Timer). In this case, you could use a Completer:
Future _destinationOnSearchChanged() async {
var timerCompleter = Completer<dynamic>();
// Cancels timer if it's still running.
_apiCityThrottle?.cancel();
// Makes API call half a second after last typed button.
_apiCityThrottle = Timer(const Duration(milliseconds: 500), () async {
timerCompleter.complete(await getCity(context, _typeAheadController.text));
});
var results = await timerCompleter.complete();
print(results);
return results;
}

Flutter method blocking, need to isolate

When I click a button and run some set state items and then call the send function, it waits until the send function is done before the set state items take effect. I have tried to make the called function async with await on the item that takes so much time BASE64.encode of image and/or video, but still it waits.
Looking for a way to not have this function block, someone mentioned isolate, but have no idea how to work that in and examples show how to work it in the entire application not just a long running function.
onPressed: () async {
setState(() {
submitting = true;
_imageFile = null;
TextInputAction.done;
});
await _sendReply();
},
Above is what I run on a ImageButton. The _sendReply is below.
_sendReply() async {
if (_newreplycontroller.text.isNotEmpty || myimagefile != null) {
//_doShowSubmitting();
DateTime dateSubmit = new DateTime.now();
if (myimagefile != null) {
if (isImage) {
List<int> imageBytes = myimagefile.readAsBytesSync();
myimage = await BASE64.encode(imageBytes);
myvideo = 'NONE';
}
if (isVideo) {
List<int> imageBytes = myvidfile.readAsBytesSync();
myvideo = await BASE64.encode(imageBytes);
myimage = 'NONE';
}
} else {
myimage = 'NONE';
myvideo = 'NONE';
}
var mymessage = _newreplycontroller.text;
ChatServerMessage mychat = new ChatServerMessage(
widget.mychat.msgkey,
'message',
widget.mychat.refid,
widget.mychat.referralname,
replysub,
oid,
oname,
pid,
pname,
sender,
sendname,
receiver,
receivename,
mymessage,
dateSubmit.toString(),
widget.mychat.grpid.toString(),
widget.mychat.prid.toString(),
myfcmtoken,
myimage,
myvideo,
myext);
_doSendReply(mychat);
} else {
}
}
From debugging I know all the time is spent on the BASE64.encode. Any ideas would be great.

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