Flutter stopwatchtimer doesn't respond to changing time - flutter

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);
}

Related

How to run on a schedule by flutter

I want to create timer app on flutter, I can't know how to schedul of run.
If you know how to do that,Would like to tell me.
This sentence was transrated,so I'm sorry if mistaken.
You can try this:
void main() {
scheduleTimeout(5 * 1000); // 5 seconds.
}
Timer scheduleTimeout([int milliseconds = 10000]) =>
Timer(Duration(milliseconds: milliseconds), handleTimeout);
void handleTimeout() { // callback function
// Do some work.
}
This function waits 5 seconds before it calls the callback handleTimeout.
Refer to this: https://api.flutter.dev/flutter/dart-async/Timer-class.html
You might try to use the work manager plugin.
workmanager. Even when the app is closed, it will execute task even when app is closed.
For ex:
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) {
print("Native called background task: $backgroundTask"); //simpleTask will be emitted here.
return Future.value(true);
});
}
void main() {
Workmanager().initialize(
callbackDispatcher, // The top level function, aka callbackDispatcher
isInDebugMode: true // If enabled it will post a notification whenever the task is running. Handy for debugging tasks
);
Workmanager().registerOneOffTask("task-identifier", "simpleTask");
runApp(MyApp());
}
You can use Timer function to achieve schedule

Cancel execution of method after it starts in 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();
}
}

Future Builder doesn't update view always

I have a future builder, and Im using two future variables:
Future<List<Notifications>>? notificationsOfTheDay; //get saved notifications from db
Future<List<NotificationCategory>>? notificationsByCat; // sort/modify "notificationsOfTheDay"
I'm sending notificationsByCat to Future Builder.
The issues:
While the app starts, the future builder is able to receive all the notifications and manipulate the data with some asynchronous operations.
But sometimes the Future Builder displays blank.
I'm also appending received notifications to the existing Future Variable notificationsOfTheDay, when sometimes the view does not update.
Code snippets are listed below:
Here is my initState
void initState() {
super.initState();
initPlatformState(); // Notification Listener Service
notificationsOfTheDay = initializeData(isToday);
}
initilizeData Method
Future<List<Notifications>> initializeData(bool istoday) async {
notificationsOfTheDay = initializeNotifications(istoday);
if (notifications!.length > 0) {
notificationsByCat = notificationsByCategory(notificationsOfTheDay); //sorting/manipulation of existing future
}
return notifications!;
}
notificationsByCategory
Future<List<NotificationCategory>> notificationsByCategory(
List<Notifications> notificationsFuture) async {
return await NotificationsHelper.getCategoryListFuture(
isToday ? 0 : 1, notificationsFuture);
}
When any new notifications are received, it is inserted into the db and the exising future is appended with the new notification;
setState(() {
notificationsOfTheDay =
appendElements(notificationsOfTheDay!, _currentNotification!);
notificationsByCat = notificationsByCategory(notifications!);
});
Future<List<Notifications>> appendElements(
Future<List<Notifications>> listFuture,
Notifications elementToAdd) async {
final list = await listFuture;
list.add(elementToAdd);
return list;
}
Can anyone please guide me to a solution? Tried many combinations. If I'm directly showing the data without modifying it according to category, it works fine.
Where am I going wrong?

Fire something inside a listener only if n seconds have passed since receiving last event

I am listening to an event, however, I don't want to print the event every time. There is an event being sent every second but I don't want my print to work every second. How can I make the print inside this listener to fire only, if 10 seconds is past since last event?
For e.g I receive an event, I use the print. I want to store the event somewhere, if 10 seconds is passed since last event, accept another event -> print and so on.
_controller.onLocationChanged.listen((event) {
print(event);
});
You may try something related to an asynchronous method as such. The following code will set the _isListening variable to true after 10 seconds, which will enable the listener to do it's action once again.
class YourClass{
bool _isListening = true;
void yourMethod() {
_controller.onLocationChanged.listen((event) {
if(_isListening){
_isListening = false;
print(event);
Future.delayed(const Duration(seconds: 10)).then((_) => _isListening=true);
}
});
}
}
Edit: Thanks to #pskink, the proper way to do it would be by using the debounceTime method. So in proper way:
_controller.onLocationChanged
.debounceTime(Duration(seconds: 10))
.listen((event) {
print(event);
});
Use the Timer like below:
Timer(const Duration(seconds: 10), (){
print("...");
});

trigger and wait for item creation in different BLoC

My approach below feels way to complicated for a simple thing I am trying to achive:
I have a list of Tasks that is managed by a TaskBloc. The UI lists all tasks and provides an execute button per task. For each click on that button I want to create and store an Action (basically the timestamp when the task-execution happened) and show a spinner while the action is created. I have an ActionBloc that manages the actions (e.g. creation or getting the history per task).
I am confused how to setup the communication between the BLoCs.
This is my approach so far.
The ActionsState just holds a list of all stored actions.
class ActionsState extends Equatable {
final List<Action> actions;
// ... copyWith, constructors etc.
}
Action is a simple PODO class holding an id and timestamp.
The ActionsBloc is capable of creating Actions in response to it's ActionCreationStarted event (holding a int taskId). Since the Action creation is performed in an async isolate there are also events ActionCreationSucceeded and ActionCreationFailed that are added by the isolate once the request finished. Both hold the Action that was either created or whose creation failed.
The TaskState:
class TaskState extends Equatable {
final Map<int, Task> tasks;
// ... copyWith, constructors, etc.
I added a executeStatus to the Task model to keep track of the status of the create request in the task list (a specific task cannot be executed multiple times in parallel, but only sequentially while different tasks can be executed in parallel):
enum Status { initial, loading, success, error }
class Task extends Equatable {
final int id;
final Status executeStatus;
// ...
}
I added events for the TaskBloc:
class TaskExecutionStarted extends TaskEvent {
final int taskId;
// ...
}
class TaskExecutionSucceeded extends TaskEvent {
final int taskId;
// ...
}
class TaskExecutionFailed extends TaskEvent {
final int taskId;
// ...
}
In the TaskBloc I implemented the mapEventToState for the new events to set the task status depending on the event, e.g. for TaskExecutionStarted:
Stream<TaskState> mapEventToState(TaskEvent event) async* {
// ...
if (event is TaskExecutionStarted) {
final taskId = event.taskId;
Task task = state.tasks[taskId]!;
yield state.copyWith(
tasks: {
...state.tasks,
taskId: task.copyWith(executeStatus: Status.loading),
},
);
}
// ...
}
So far this enables the UI to show a spinner per Task but the ActionBloc does not yet know that it should record a new Action for that task and the TaskBloc does not know when to stop showing the spinner.
PROBLEM
Now the part where I am lost is that I need to actually trigger the ActionBloc to create an action and get an TaskExecutionSucceeded (or ...Failed) event afterwards. I thought about using a listener on the ActionsBloc, but it only provides the state and not the events of the ActionsBloc (I would need to react to the ActionCreationSucceeded event, but listening to events of an other bloc feels like an anti-pattern (?!) and I do not even know how to set it up).
The core of the problem is, that I may listen on the ActionsBloc state but I don't know how to distinguish for which actions of the state I would need to trigger a TaskExecutionSucceeded event.
Anyway, I gave the TaskBloc a reference to ActionsBloc:
class TaskBloc extends Bloc<TaskEvent, TaskState> {
final ActionsBloc actionsBloc;
late final StreamSubscription actionsSubscription;
// ...
TaskBloc({
// ...
required this.actionsBloc,
}) : super(TaskState.initial()) {
actionsSubscription = actionsBloc.listen((state) {
/* ... ??? ... Here I don't know how to distinguish for which actions of the state
I would somehow need to trigger a `TaskExecutionSucceeded` event. */
});
};
// ...
}
For the sake of completeness, triggering creation of the Action is simple by adding the corresponding event to the ActionBloc as response to the TaskExecutionStarted:
Stream<TaskState> mapEventToState(TaskEvent event) async* {
// ...
// ... set executeStatus: Status.loading as shown above ...
// trigger creating a new action
actionsBloc.add(ActionCreationStarted(taskId: taskId));
// ...
Of course I aim at clear separation of concerns, single source of truth and other potential sources for accidential complexity regarding app state structure - but overall this approach (which still has said problem unsolved before working) feels way to complicated just to store a timestamp per action of a task and keep track of the action-creation-request.
I appreciate that you read so far (!) and I am very happy about hints towards a clean architecture for that use case.
So what we ended up doing is the following:
Introduce a lastCreatedState in ActionsState that represents the status of the last created action.
Instead of always listening to the ActionsBloc all the time we listen to its state temporarily when task execution is happening and remember the listener per event.
Once we got a change in the ActionsBloc lastCreatedState state that indicates success or failure of our task we remove the listener and react to it.
Something along the lines of this:
/// When a task is executed, we trigger an action creation and wait for the
/// [ActionsBloc] to signal success or failure for that task.
/// The mapping maps taskId => subscription.
final Map<int, StreamSubscription> _actionsSubscription = {};
Stream<TaskState> mapEventToState(TaskEvent event) async* {
// ...
// trigger creation of an action
actionsBloc.add(ActionCreationStarted(taskId: taskId));
// listen to the result
// remove any previous listeners
if (_actionsSubscription[taskId] != null) {
await _actionsSubscription[taskId]!.cancel();
}
StreamSubscription<ActionsState>? listener;
listener = actionsBloc.stream.listen((state) async {
final status = state.lastCreatedState?.status;
final doneTaskId = state.lastCreatedState?.action?.taskId;
if (doneTaskId == taskId &&
(status == Status.success || status == Status.error)) {
await listener?.cancel(); // stop listening
add(TaskExecutionDone(taskId: taskId, status: status!));
}
});
_actionsSubscription[taskId] = listener;
}
#override
Future<void> close() {
_actionsSubscription.values.forEach((s) {
s.cancel();
});
return super.close();
}
It is not perfect: It requires the pollution of the ActionsState and it requires the TaskBloc to not be disposed before all listeners have finished (or at least have other stuff that ensures the state is hydrated and synced on creation) and the polluted sate.
While the internals are a little more complicated it keeps things separated and makes using the blocs a breeze. 🌈