Cancel execution of method after it starts in Flutter - flutter

Consider this method:
Future<void> methodWithAlotOfSteps()async{
callMethodA();
await callMethodB();
...
return ...;
}
which makes some computation. Suppose I want the user to be able to stop this process at any point in time (when he taps cancel button for example).
How can I stop the execution of the above method no matter what line in the method the "program counter" has reached when the user presses cancel.
I am looking for something like methodWithAlotOfSteps.cancel();.
I tried using CancelableCompleter, but even though the Future is cancelled and onCancel method of the completer is called, but the function continues execution.
I know I can set a boolean flag and check it after each "step" ("line", "call to a method"),such as :
Future<void> methodWithAlotOfSteps()async{
if(!completer.isCancelled)
callMethodA();
if(!completer.isCancelled)
await callMethodB();
...
return ...;
}
but is there a better way of doing this?

As #Jamesdlin suggested, the apparent way is to check the cancelable completer's state after each async gap:
class MyService{
CancelableCompleter completer = CancelableCompleter();
Future<void> doSomething() async {
doSomeSyncWork(); // Sync work
doAnotherSyncWork(); // Sync work
await doSomeAsyncWork(); // Async work, this will return control to event loop and make it possible to, for example, press a button to cancel the future.
// here we know we have lost control for a while, so we must check if we have been cancelled
if (completer.isCompleted) {
return;
}
doSomeMoreSyncWork();
await doSomeMoreAsyncWork();
// here we know we have lost control for a while, so we must check if we have been cancelled
if (completer.isCompleted) {
return;
}
...
completer.complete();
}
}

Related

Is it good approach to cancel WorkManger task in executeTask() function in flutter

I have to cancel/dispose workmanager task as soon as it gets completed, but most of the times app is not in running state. So is it good or bad to cancel that task in executeTask() function.
Here is code example:
Register task first
Workmanager().registerOneOffTask('taskABC','taskABC',
inputData: {'data':'some data here'},);
And here is an code inside callbackDispathcer()
Workmanager().executeTask((taskName, inputData) async {
try {
switch(taskName) {
case 'taskABC':
//do something here
Workmanager().cancelByUniqueName('taskABC');
break;
}
return true;
} catch(e) {
debugPrint('---> Error in work manager execution taskName[$taskName] with inputData[$inputData] | Exception: $e');
return false;
}});
I just need to know what could be the best way to cancel/dispose a task, when the app is not in running state.

How to handle reading from database when API-request hasn't finished yet to save to database in Flutter?

For my App i'm loading the link for the background-image for each screen from my API.
Right after the query that downloads and saves the image-link, i'm querying the link from the database.
The problem now is, that the function isn't waiting for the download and saving to finish although i'm using await, therefor i get an empty result from the database and get an error from the imageloader.
Future<String> downloadAsset(String name) async {
final Map<String, String> _json = {
'mandant': Config.mandant,
'name': name
};
final query = MakePost('get_app_assets', false, _json, saveAsset);
await query.queryAPI(); // Function won't wait for this to finish
String ret = await dbAppAssets.getText(name); // Get link from database
return ret;
}
I've already tried to use .then(), but the same thing happens.
This only happens initially on the first call of each screen, but how is this normally beeing handled?
I'm using riverpod with futureProviders if that matters.
I do not know where you are using the downloadAsset function, but when you use Future on a function you should also await the function where you are using it, for example:
Future<void> _reverseScrollToTopAnimation() async {
await controller!.reverse();
_showBackToTopButton = false; // hide the back-to-top button
}
then, wherever you call it you should also await that function:
await _reverseScrollToTopAnimation();
If not the function will act as a synchronous operation even though you are using await inside the function.

Flutter stopwatchtimer doesn't respond to changing time

I use this package https://pub.dev/packages/stop_watch_timer in my app to keep track of the music that is playing. However if I want to change the song by changing the time on the stopwatch it says that I have to reset the timer first which I have already done. If I press the button for the second time it works. This is the code:
final StopWatchTimer _stopWatchTimer = StopWatchTimer(
mode: StopWatchMode.countUp,
onChangeRawSecond: (value) => print('onChangeRawSecond $value'),
);
void change_timer_value(int song_index) {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
_stopWatchTimer.setPresetSecondTime(new_time); // this is where I set new time
}
I don't know how to get around this. I have already created an issue on the creators GitHub but no response. So there's somebody who can help me here
As you mentioned in the github issue, it looks like the root cause of your issue is that the reset action takes place asynchronously, and so hasn't gone through yet by the time you try to set the time.
One way to get around this is to define your own async function which resets the stopwatch, then waits for the action to complete before returning:
Future<void> _resetTimer() {
final completer = Completer<void>();
// Create a listener that will trigger the completer when
// it detects a reset event.
void listener(StopWatchExecute event) {
if (event == StopWatchExecute.reset) {
completer.complete();
}
}
// Add the listener to the timer's execution stream, saving
// the sub for cancellation
final sub = _stopWatchTimer.execute.listen(listener);
// Send the 'reset' action
_stopWatchTimer.onExecute.add(StopWatchExecute.reset);
// Cancel the sub after the future is fulfilled.
return completer.future.whenComplete(sub.cancel);
}
Usage:
void change_timer_value(int song_index) {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
_resetTimer().then(() {
_stopWatchTimer.setPresetSecondTime(new_time);
});
}
Or (with async/await):
void change_timer_value(int song_index) async {
int new_time = TimerState(
song_index: song_index,
record_side: current_side_list(
record_sides[selectedValue], widget.album_data))
.get_start_value();
print(new_time);
await _resetTimer();
_stopWatchTimer.setPresetSecondTime(new_time);
}

Flutter- How to know AudioService is stopped?

I am working with audio_service flutter package. I want to pop a player page if Audio Service stops. How to get the Audio Service stopped event? I didn't find any events to check if service is stopped
(Answer update: Since v0.18, the service is effectively always running while the app is running, so there is no longer a need to check. The following answer is for v0.17 and earlier.)
AudioService.running will emit true when the service is running and false when it is not.
To listen to when it changes from true to false, you could try this:
// Cast runningStream from dynamic to the correct type.
final runningStream =
AudioService.runningStream as ValueStream<bool>;
// Listen to stream pairwise and observe when it becomes false
runningStream.pairwise().listen((pair) {
final wasRunning = pair.first;
final isRunning = pair.last;
if (wasRunning && !isRunning) {
// take action
}
});
If you instead want to listen to the stopped playback state, you need to ensure that your background audio task actually emits that state change in onStop:
#override
Future<void> onStop() async {
await _player.dispose();
// the "await" is important
await AudioServiceBackground.setState(
processingState: AudioProcessingState.stopped);
// Shut down this task
await super.onStop();
}
This way, you can listen for this state in the UI:
AudioService.playbackStateStream.listen((state) {
if (state.processingState == AudioProcessingState.stopped)) {
// take action
}
});

Ignore multiple button taps after first one on iPhone webapp using jQuery Mobile?

Assume button A in an HTML5 webapp built with jQuery Mobile.
If someone taps button A, we call foo(). Foo() should get called once even if the user double taps button A.
We tried using event.preventDefault(), but that didn't stop the second tap from invoking foo(). event.stopImmediatePropagation() might work, but it also stops other methods further up the stack and may not lead to clean code maintenance.
Other suggestions? Maintaining a tracking variable seems like an awfully ugly solution and is undesirable.
You can set a flag and check if it's OK to run the foo() function or unbind the event for the time you don't want the user to be able to use it and then re-bind the event handler after a delay (just a couple options).
Here's what I would do. I would use a timeout to exclude the subsequent events:
$(document).delegate('#my-page-id', 'pageinit', function () {
//setup a flag to determine if it's OK to run the event handler
var okFlag = true;
//bind event handler to the element in question for the `click` event
$('#my-button-id').bind('click', function () {
//check to see if the flag is set to `true`, do nothing if it's not
if (okFlag) {
//set the flag to `false` so the event handler will be disabled until the timeout resolves
okFlag = false;
//set a timeout to set the flag back to `true` which enables the event handler once again
//you can change the delay for the timeout to whatever you may need, note that units are in milliseconds
setTimeout(function () {
okFlag = true;
}, 300);
//and now, finally, run your original event handler
foo();
}
});
});
I've created a sample here http://jsfiddle.net/kiliman/kH924/
If you're using <a data-role="button"> type buttons, there is no 'disabled' status, but you can add the appropriate class to give it the disabled look.
In your event handler, check to see if the button has the ui-disabled class, and if so, you can return right away. If it doesn't, add the ui-disabled class, then call foo()
If you want to re-enable the button, simply remove the class.
$(function() {
$('#page').bind('pageinit', function(e, data) {
// initialize page
$('#dofoo').click(function() {
var $btn = $(this),
isDisabled = $btn.hasClass('ui-disabled');
if (isDisabled) {
e.preventDefault();
return;
}
$btn.addClass('ui-disabled');
foo();
});
});
function foo() {
alert('I did foo');
}
});