Skip to content

Commit

Permalink
DispatchAndWaitAll, DispatchAll.
Browse files Browse the repository at this point in the history
  • Loading branch information
marcglasberg committed Apr 2, 2024
1 parent b4c5bd3 commit afa136d
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 9 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@ an <a href="https://github.com/marcglasberg/SameAppDifferentTech/blob/main/Mobil
Async Redux App Example Repository</a> in GitHub for a full-fledged example with a complete app
showcasing the fundamentals and best practices described in the AsyncRedux README.md file._

# 22.5.0

* You can now use `dispatchAll()` and `dispatchAndWaitAll()` to dispatch multiple actions
in parallel. For example:

```dart
class BuyAndSell extends Action {
Future<AppState> reduce() async {
await dispatchAndWaitAll([
BuyAction('IBM'),
SellAction('TSLA')
]);
return state.copy(message: 'New cash balance is ${state.cash}');
}
}
```

# 22.4.9

* For those who use `flutter_hooks`, you can now use the
Expand Down
67 changes: 60 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,61 @@ class MyWidget extends StatelessWidget {
}}
```

Your actions can also dispatch other actions, and you can even use `dispatchAndWait` to
Your actions can also dispatch other actions, and use `dispatchAndWait` to
wait for an action to finish:

```dart
class LoadTextAndIncrement extends Action {
Future<AppState> reduce() async {
Future<AppState> reduce() async {
// Dispatch and wait for the action to finish
await dispatchAndWait(LoadText());
// Only then, increment the state
return state.copy(count: state.count + 1);
}}
}
}
```

You can also dispatch actions in parallel and wait for them to finish:

```dart
class BuyAndSell extends Action {
Future<AppState> reduce() async {
// Dispatch and wait for both actions to finish
await dispatchAndWaitAll([
BuyAction('IBM'),
SellAction('TSLA')
]);
return state.copy(message: 'New cash balance is ${state.cash}');
}
}
```

You can also use waitCondition to wait until the state changes in a certain way:

```dart
class SellStockForPrice extends Action {
final String stock;
final double limitPrice;
SellStockForPrice(this.stock, this.limitPrice);
Future<AppState?> reduce() async {
// Wait until the stock price is higher than the limit price
await waitCondition(
(state) => state.stocks[stock].price >= limitPrice
);
dispatch(SellStock(stock));
// No further state change
return null;
}
```

You can add **mixins** to your actions, to accomplish common tasks:
Expand Down Expand Up @@ -878,6 +923,8 @@ In more detail:

* `context.dispatch()`, `.dispatchAndWait()` and `.dispatchSync()` - Dispatch an action.

* `context.dispatchAll()`, and `.dispatchAndWaitAll()` - Dispatch multiple actions.

* `context.isWaiting()` - Returns true if the given action type is currently being processed.

* `context.isFailed()` - Returns true if an action of the given type failed with an `UserException`.
Expand Down Expand Up @@ -1566,9 +1613,9 @@ dialog to the user, but you don't want to interrupt the action by throwing an ex
Testing involves waiting for an action to complete its dispatch process,
or for the store state to meet a certain condition. After this, you can verify the current
state or action using the
methods `store.dispatchAndWait`, `store.waitCondition`, `store.waitActionCondition`,
`store.waitAllActions`, `store.waitActionType`, `store.waitAllActionTypes`,
and `store.waitAnyActionTypeFinishes`. For example:
methods `store.dispatchAndWait`, `store.dispatchAndWaitAll`, `store.waitCondition`,
`store.waitActionCondition`, `store.waitAllActions`, `store.waitActionType`,
`store.waitAllActionTypes`, and `store.waitAnyActionTypeFinishes`. For example:

```dart
// Wait for some action to dispatch and check the state.
Expand Down Expand Up @@ -1598,6 +1645,10 @@ dispatch(action2);
await store.waitAllActions([action1, action2]);
expect(store.state.portfolio.containsAll('IBM', 'TSLA'), isFalse);
// Another way to dispatch two actions in PARALLEL and wait for them.
await store.dispatchAndWaitAll([BuyAction('IBM'), BuyAction('TSLA')]);
expect(store.state.portfolio.containsAll('IBM', 'TSLA'), isFalse);
// Wait until no actions are in progress.
dispatch(BuyStock('IBM'));
dispatch(BuyStock('TSLA'));
Expand Down Expand Up @@ -2620,7 +2671,9 @@ Future<void> downloadStuff() => dispatchAndWait(DownloadStuffAction());
return RefreshIndicator(
onRefresh: downloadStuff;
child: ListView(...),
```
```

Or, if you have multiple actions you can use `dispatchAndWaitAll`.

Try running
the: <a href="https://github.com/marcglasberg/async_redux/blob/master/example/lib/main_dispatch_future.dart">
Expand Down
61 changes: 61 additions & 0 deletions lib/src/redux_action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ abstract class ReduxAction<St> {
/// Method [dispatch] is of type [Dispatch].
///
/// See also:
/// - [dispatchAll] which dispatches all given actions in parallel.
/// - [dispatchSync] which dispatches sync actions, and throws if the action is async.
/// - [dispatchAndWaitAll] which dispatches all given actions, and returns a Future.
/// - [dispatchAndWait] which dispatches both sync and async actions, and returns a Future.
///
Dispatch<St> get dispatch => _store.dispatch;
Expand All @@ -112,7 +114,9 @@ abstract class ReduxAction<St> {
///
/// See also:
/// - [dispatch] which dispatches both sync and async actions.
/// - [dispatchAll] which dispatches all given actions in parallel.
/// - [dispatchAndWait] which dispatches both sync and async actions, and returns a Future.
/// - [dispatchAndWaitAll] which dispatches all given actions, and returns a Future.
///
DispatchSync<St> get dispatchSync => _store.dispatchSync;

Expand Down Expand Up @@ -144,10 +148,67 @@ abstract class ReduxAction<St> {
///
/// See also:
/// - [dispatch] which dispatches both sync and async actions.
/// - [dispatchAll] which dispatches all given actions in parallel.
/// - [dispatchAndWaitAll] which dispatches all given actions, and returns a Future.
/// - [dispatchSync] which dispatches sync actions, and throws if the action is async.
///
DispatchAndWait<St> get dispatchAndWait => _store.dispatchAndWait;

/// Dispatches all given [actions] in parallel, applying their reducers, and possibly changing
/// the store state. The actions may be sync or async. It returns a [Future] that resolves when
/// ALL actions finish.
///
/// ```dart
/// var actions = await store.dispatchAndWaitAll([BuyAction('IBM'), SellAction('TSLA')]);
/// ```
///
/// Note this is exactly the same as doing:
///
/// ```dart
/// var action1 = BuyAction('IBM');
/// var action2 = SellAction('TSLA');
/// dispatch(action1);
/// dispatch(action2);
/// await store.waitAllActions([action1, action2], completeImmediately = true);
/// var actions = [action1, action2];
/// ```
///
/// If you pass the [notify] parameter as `false`, widgets will not necessarily rebuild because
/// of these actions, even if they change the state.
///
/// Note: While the state change from the action's reducers will have been applied when the
/// Future resolves, other independent processes that the action may have started may still
/// be in progress.
///
/// See also:
/// - [dispatch] which dispatches both sync and async actions.
/// - [dispatchAndWait] which dispatches both sync and async actions, and returns a Future.
/// - [dispatchSync] which dispatches sync actions, and throws if the action is async.
/// - [dispatchAll] which dispatches all given actions in parallel.
///
Future<List<ReduxAction<St>>> Function(List<ReduxAction<St>> actions, {bool notify})
get dispatchAndWaitAll => _store.dispatchAndWaitAll;

/// Dispatches all given [actions] in parallel, applying their reducer, and possibly changing
/// the store state. It returns the same list of [actions], so that you can instantiate them
/// inline, but still get a list of them.
///
/// ```dart
/// var actions = dispatchAll([BuyAction('IBM'), SellAction('TSLA')]);
/// ```
///
/// If you pass the [notify] parameter as `false`, widgets will not necessarily rebuild because
/// of these actions, even if it changes the state.
///
/// See also:
/// - [dispatch] which dispatches both sync and async actions.
/// - [dispatchAndWait] which dispatches both sync and async actions, and returns a Future.
/// - [dispatchAndWaitAll] which dispatches all given actions, and returns a Future.
/// - [dispatchSync] which dispatches sync actions, and throws if the action is async.
///
List<ReduxAction<St>> Function(List<ReduxAction<St>> actions, {bool notify}) get dispatchAll =>
_store.dispatchAll;

/// This is an optional method that may be overridden to run during action
/// dispatching, before `reduce`. If this method throws an error, the
/// `reduce` method will NOT run, but the method `after` will.
Expand Down
75 changes: 75 additions & 0 deletions lib/src/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,7 @@ class Store<St> {
/// See also:
/// - [dispatchSync] which dispatches sync actions, and throws if the action is async.
/// - [dispatchAndWait] which dispatches both sync and async actions, and returns a Future.
/// - [dispatchAll] which dispatches all given actions in parallel.
///
FutureOr<ActionStatus> dispatch(ReduxAction<St> action, {bool notify = true}) =>
_dispatch(action, notify: notify);
Expand All @@ -1221,6 +1222,8 @@ class Store<St> {
/// See also:
/// - [dispatch] which dispatches both sync and async actions.
/// - [dispatchAndWait] which dispatches both sync and async actions, and returns a Future.
/// - [dispatchAndWaitAll] which dispatches all given actions, and returns a Future.
/// - [dispatchAll] which dispatches all given actions in parallel.
///
ActionStatus dispatchSync(ReduxAction<St> action, {bool notify = true}) {
if (!action.isSync()) {
Expand Down Expand Up @@ -1256,11 +1259,83 @@ class Store<St> {
///
/// See also:
/// - [dispatch] which dispatches both sync and async actions.
/// - [dispatchAndWaitAll] which dispatches all given actions, and returns a Future.
/// - [dispatchSync] which dispatches sync actions, and throws if the action is async.
/// - [dispatchAll] which dispatches all given actions in parallel.
///
Future<ActionStatus> dispatchAndWait(ReduxAction<St> action, {bool notify = true}) =>
Future.value(_dispatch(action, notify: notify));

/// Dispatches all given [actions] in parallel, applying their reducer, and possibly changing
/// the store state. It returns the same list of [actions], so that you can instantiate them
/// inline, but still get a list of them.
///
/// ```dart
/// var actions = dispatchAll([BuyAction('IBM'), SellAction('TSLA')]);
/// ```
///
/// If you pass the [notify] parameter as `false`, widgets will not necessarily rebuild because
/// of these actions, even if it changes the state.
///
/// See also:
/// - [dispatch] which dispatches both sync and async actions.
/// - [dispatchAndWait] which dispatches both sync and async actions, and returns a Future.
/// - [dispatchAndWaitAll] which dispatches all given actions, and returns a Future.
/// - [dispatchSync] which dispatches sync actions, and throws if the action is async.
///
List<ReduxAction<St>> dispatchAll(List<ReduxAction<St>> actions, {bool notify = true}) {
for (var action in actions) {
dispatch(action, notify: notify);
}
return actions;
}

/// Dispatches all given [actions] in parallel, applying their reducers, and possibly changing
/// the store state. The actions may be sync or async. It returns a [Future] that resolves when
/// ALL actions finish.
///
/// ```dart
/// var actions = await store.dispatchAndWaitAll([BuyAction('IBM'), SellAction('TSLA')]);
/// ```
///
/// Note this is exactly the same as doing:
///
/// ```dart
/// var action1 = BuyAction('IBM');
/// var action2 = SellAction('TSLA');
/// dispatch(action1);
/// dispatch(action2);
/// await store.waitAllActions([action1, action2], completeImmediately = true);
/// var actions = [action1, action2];
/// ```
///
/// If you pass the [notify] parameter as `false`, widgets will not necessarily rebuild because
/// of these actions, even if they change the state.
///
/// Note: While the state change from the action's reducers will have been applied when the
/// Future resolves, other independent processes that the action may have started may still
/// be in progress.
///
/// See also:
/// - [dispatch] which dispatches both sync and async actions.
/// - [dispatchAndWait] which dispatches both sync and async actions, and returns a Future.
/// - [dispatchSync] which dispatches sync actions, and throws if the action is async.
/// - [dispatchAll] which dispatches all given actions in parallel.
///
Future<List<ReduxAction<St>>> dispatchAndWaitAll(
List<ReduxAction<St>> actions, {
bool notify = true,
}) async {
var futures = <Future<ActionStatus>>[];

for (var action in actions) {
futures.add(dispatchAndWait(action, notify: notify));
}
await Future.wait(futures);

return actions;
}

@Deprecated("Use `dispatchAndWait` instead. This will be removed.")
Future<ActionStatus> dispatchAsync(ReduxAction<St> action, {bool notify = true}) =>
dispatchAndWait(action, notify: notify);
Expand Down
Loading

0 comments on commit afa136d

Please sign in to comment.