waitCondition method

Future<ReduxAction<St>?> waitCondition(
  1. bool condition(
    1. St
    ), {
  2. bool completeImmediately = true,
  3. int? timeoutMillis,
})

Returns a future which will complete when the given state condition is true.

If completeImmediately is true (the default), the future will complete immediately and throw no error. Otherwise, this method will throw StoreException if the condition is already true when the method is called. Note: The default here is true, while in the other wait methods like waitActionCondition it's false. This makes sense because of the different use cases for these methods.

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.

This method is useful in tests, and it returns the action which changed the store state into the condition, in case you need it:

var action = await store.waitCondition((state) => state.name == "Bill");
expect(action, isA<ChangeNameAction>());

This method is also eventually useful in production code, in which case you should avoid waiting for conditions that may take a very long time to complete, as checking the condition is an overhead to every state change.

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.

Implementation

Future<ReduxAction<St>?> waitCondition(
  bool Function(St) condition, {
  //
  /// If `completeImmediately` is `true` (the default), the future will complete
  /// immediately and throw no error. Otherwise, this method will throw [StoreException]
  /// if the condition is already true when the method is called. Note: The default here
  /// is `true`, while in the other `wait` methods like [waitActionCondition] it's `false`.
  /// This makes sense because of the different use cases for these methods.
  bool completeImmediately = 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,
}) async {
  //
  // If the condition is already true when `waitCondition` is called.
  if (condition(_state)) {
    // Complete and return null (no trigger action).
    if (completeImmediately)
      return Future.value(null);
    // else throw an error.
    else
      throw StoreException("Awaited state condition was already true, "
          "and the future completed immediately.");
  }
  //
  else {
    var completer = Completer<ReduxAction<St>?>();

    _stateConditionCompleters[condition] = completer;

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

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

    return future;
  }
}