waitActionCondition method

  1. @visibleForTesting
Future<(Set<ReduxAction<St>>, ReduxAction<St>?)> waitActionCondition(
  1. bool condition(
    1. Set<ReduxAction<St>> actions,
    2. ReduxAction<St>? triggerAction
    ), {
  2. bool completeImmediately = false,
  3. String completedErrorMessage = "Awaited action condition was already true",
  4. int? timeoutMillis,
})

Returns a future that completes when some actions meet the given condition.

If completeImmediately is false (the default), this method will throw StoreException if the condition was already true when the method was called. Otherwise, the future will complete immediately and throw no error.

The condition is a function that takes the set of actions "in progress", as well as an action that just entered the set (by being dispatched) or left the set (by finishing dispatching). The function should return true when the condition is met, and false otherwise. For example:

var action = await store.waitActionCondition((actionsInProgress, triggerAction) { ... }

You get back an unmodifiable set of the actions being dispatched that met the condition, as well as the action that triggered the condition by being added or removed from the set.

Note: The condition is only checked when some action is dispatched or finishes dispatching. It's not checked every time action statuses change.

You may also provide a timeoutMillis, which by default is 10 minutes. To disable the timeout, make it -1. If you want, you can modify defaultTimeoutMillis to change the default timeout.

Examples:

// Dispatches an actions that changes the state, then await for the state change:
expect(store.state.name, 'John')
dispatch(ChangeNameAction("Bill"));
var action = await store.waitCondition((state) => state.name == "Bill");
expect(action, isA<ChangeNameAction>());
expect(store.state.name, 'Bill');

// Dispatches actions and wait until no actions are in progress.
dispatch(BuyStock('IBM'));
dispatch(BuyStock('TSLA'));
await waitAllActions([]);
expect(state.stocks, ['IBM', 'TSLA']);

// Dispatches two actions in PARALLEL and wait for their TYPES:
expect(store.state.portfolio, ['TSLA']);
dispatch(BuyAction('IBM'));
dispatch(SellAction('TSLA'));
await store.waitAllActionTypes([BuyAction, SellAction]);
expect(store.state.portfolio, ['IBM']);

// Dispatches actions in PARALLEL and wait until no actions are in progress.
dispatch(BuyAction('IBM'));
dispatch(BuyAction('TSLA'));
await store.waitAllActions([]);
expect(store.state.portfolio.containsAll('IBM', 'TSLA'), isFalse);

// Dispatches two actions in PARALLEL and wait for them:
let action1 = BuyAction('IBM');
let action2 = SellAction('TSLA');
dispatch(action1);
dispatch(action2);
await store.waitAllActions([action1, action2]);
expect(store.state.portfolio.contains('IBM'), isTrue);
expect(store.state.portfolio.contains('TSLA'), isFalse);

// Dispatches two actions in SERIES and wait for them:
await dispatchAndWait(BuyAction('IBM'));
await dispatchAndWait(SellAction('TSLA'));
expect(store.state.portfolio.containsAll('IBM', 'TSLA'), isFalse);

// Wait until some action of a given type is dispatched.
dispatch(DoALotOfStuffAction());
var action = store.waitActionType(ChangeNameAction);
expect(action, isA<ChangeNameAction>());
expect(action.status.isCompleteOk, isTrue);
expect(store.state.name, 'Bill');

// Wait until some action of the given types is dispatched.
dispatch(ProcessStocksAction());
var action = store.waitAnyActionTypeFinishes([BuyAction, SellAction]);
expect(store.state.portfolio.contains('IBM'), isTrue);

See also: waitCondition - Waits until the state is in a given condition. waitActionCondition - Waits until the actions in progress meet a given condition. waitAllActions - Waits until the given actions are NOT in progress, or no actions are in progress. waitActionType - Waits until an action of a given type is NOT in progress. waitAllActionTypes - Waits until all actions of the given type are NOT in progress. waitAnyActionTypeFinishes - Waits until ANY action of the given types finish dispatching.

You should only use this method in tests.

Implementation

@visibleForTesting
Future<(Set<ReduxAction<St>>, ReduxAction<St>?)> waitActionCondition(
  //
  //
  /// The condition receives the current actions in progress, and the action that triggered the condition.
  bool Function(Set<ReduxAction<St>> actions, ReduxAction<St>? triggerAction) condition, {
  //
  /// If `completeImmediately` is `false` (the default), this method will throw an error if the
  /// condition is already true when the method is called. Otherwise, the future will complete
  /// immediately and throw no error.
  bool completeImmediately = false,
  //
  /// Error message in case the condition was already true when the method was called,
  /// and `completeImmediately` is false.
  String completedErrorMessage = "Awaited action condition was already true",
  //
  /// The maximum time to wait for the condition to be met. The default is 10 minutes.
  /// To disable the timeout, make it -1.
  int? timeoutMillis,
}) {
  //
  // If the condition is already true when `waitActionCondition` is called.
  if (condition(actionsInProgress(), null)) {
    // Complete and return the actions in progress and the trigger action.
    if (completeImmediately)
      return Future.value((actionsInProgress(), null));
    // else throw an error.
    else
      throw StoreException(completedErrorMessage + ", and the future completed immediately.");
  }
  //
  else {
    var completer = Completer<(Set<ReduxAction<St>>, ReduxAction<St>?)>();

    _actionConditionCompleters[condition] = completer;

    int timeout = timeoutMillis ?? defaultTimeoutMillis;
    var future = completer.future;

    if (timeout >= 0)
      future = completer.future.timeout(
        Duration(milliseconds: timeout),
        onTimeout: () {
          _actionConditionCompleters.remove(condition);
          throw TimeoutException(null, Duration(milliseconds: timeout));
        },
      );

    return future;
  }
}