marcglasberg / async_redux

Flutter Package: A Redux version tailored for Flutter, which is easy to learn, to use, to test, and has no boilerplate. Allows for both sync and async reducers.
Other
229 stars 41 forks source link

Debounce (protect against accidental multiple operations) #75

Open subzero911 opened 4 years ago

subzero911 commented 4 years ago

How to implement protection against too often heavy operations? For instance, if I pressed the "Save" button quickly several times. I don't want to save multiple times. I want to wait a 500 ms after last press and save only once.

In MobX we have a ready-made reaction 'delay' parameter (check the 101-105 lines): https://github.com/brianegan/flutter_architecture_samples/blob/master/mobx/lib/stores/todo_store.dart

In a pure Provider architecture I'd use an RxDart debounce() to control saving:

class Store with ChangeNotifier {
final _saveController = StreamController<item>(); //helper stream
final List<Item> _list = []; //data

Store() { _saveController.stream.debounce(ms: 500).listen => // do your save here  }

 void add(item) { //action
 _list.add(item);
 notifyListeners();
  _saveController.add(item); //add event
 }
}

But I have no idea, how to create similar functionality with Async Redux, because it's not recommended to keep streams in the store.

marcglasberg commented 4 years ago

Please, first read this section of the documentation: https://pub.dev/packages/async_redux#progress-indicators

There are many ways to do that:

1) You can use the Wait class in the store to create a modal barrier (in the UI) to prevent the button from being tapped, or to turn off the button's onTap callback, while the save is being performed.

2) Actions have an abortDispatch method. You can use the Wait class in the store to abort the dispatch while the save is being performed (or just return null from the reducer).

3) You can add a static bool isSaving field to the action. Make it true when the action starts (using the action's before method), and make it false when it finishes (using the action's after method). Then abort the dispatch if the bool is already true. Note the abortDispatch method runs before the before method.

4) If you just want to have a time-based debouncing that cancels actions before some elapsed time, add a static static DateTime last field to the action. Save the action's stateTimestamp to this variable when the action starts (using the action's before method), and make it null when it finishes (using the action's after method). Then abort the dispatch if the action's stateTimestamp is not at least x seconds from saved timestamp.

5) However, if you want to have a time-based debouncing that groups actions within some elapsed time, it's a bit more complex. The store itself does this when it needs to call the state persistor. But it's also more complex because it checks not only the elapsed time, but also if the previous save has finished. I guess I could add some more functionality to the AsyncRedux's Wait class to allow for these more complex deboucings, but I'm not sure it's necessary. Usually I just prevent the button from being tapped during the save process, in the UI, using the Wait class, and that's usually better than a time-based debouncing.

subzero911 commented 4 years ago

Usually I just prevent the button from being tapped during the save process, in the UI, using the Wait class, and that's usually better than a time-based debouncing.

It was just an example with button. I could move a slider back and forth and save after 2 seconds when I stopped. I cannot block UI in this case.

marcglasberg commented 4 years ago

Yes, as I said, the store does that with the persistor. I will see if I can expose this functionality for general use.

jans-y commented 3 years ago

For that purpose, I am using this library:

https://pub.dev/packages/easy_debounce

I am wrapping all dispatch() calls that I need to debounce with it.