NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev · #3162 most downloaded on pub.dev
The modern version of Redux. State management that's simple to learn and easy to use; Powerful enough to handle complex applications with millions of users; Testable.
Last release 2 days ago
06 Oct 2026
Release timing varies
gaps range from 9 days to 2 months
Some releases are documented
notes for 30 of the last 60 stable releases
1 version withdrawn
withdrawn after publishing
7 years old
277 releases · first in 2019
Nothing published for this version
Nothing published for this version
You can now use the UnlimitedRetryCheckInternet to check if there is internet when you run some action that needs it. If there is no internet, the act
You can now use the UnlimitedRetryCheckInternet to check if there is
internet when you
run some action that needs it. If there is no internet, the action will abort
silently
and then retried unlimited times, until there is internet. It will also retry
if there
is internet but the action failed.
You can provide a CloudSync object to the store constructor. It's similar to
the Persistor, but can be used to synchronize the state of the application
with the server. This is experimental.
Fixed isWaiting() for checking multiple actions and when state doesn't
change.
One column per quarter.
Nothing published for this version
New: AsyncRedux website at https://asyncredux.com
New: AsyncRedux website at https://asyncredux.com
New: AsyncRedux for React
Nothing published for this version
Fixed isWaiting() when action fails.
isWaiting() when action fails.* Fixed disposeProps.
disposeProps.Now using connectivity_plus: 6.0.3 or up.
connectivity_plus: 6.0.3 or up.You can now use dispatchAll() and dispatchAndWaitAll() to dispatch multiple actions in parallel. For example:
You can now use dispatchAll() and dispatchAndWaitAll() to dispatch
multiple actions
in parallel. For example:
class BuyAndSell extends Action {
Future<AppState> reduce() async {
await dispatchAndWaitAll([
BuyAction('IBM'),
SellAction('TSLA')
]);
return state.copy(message: 'New cash balance is ${state.cash}');
}
}
For those who use flutter_hooks, you can now use the new https://pub.dev/packages/flutter_hooks_async_redux package to add Redux to flutter_hooks.
flutter_hooks, you can now use the
new https://pub.dev/packages/flutter_hooks_async_redux package
to add Redux to flutter_hooks.Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
In the reduce method of your actions you can now access the _initial state_ of the action, by using the initialState getter. In other words, you have
In the reduce method of your actions you can now access the initial state
of the action, by using the initialState getter. In other words, you have
access to a copy of the state as it was when the action was first dispatched.
This is useful when you need to calculate some value asynchronously, and then
you only want to apply the result to the state if that value hasn't changed in
the meantime. For example:
class MyAction extends ReduxAction<AppState> {
Future<AppState> reduce() async {
var newValue = await someAsyncStuff();
if (state.value == initialState.value) return state.copyWith(value: newValue);
else return null;
}
}
Nothing published for this version
You can now use var isWaiting = context.isWaiting(MyAction) to check if an async action of the given type is currently being processed. You can then u
You can now use var isWaiting = context.isWaiting(MyAction) to check if an
async action of the given type is currently being processed. You can then use
this
boolean to show a loading spinner in your widget.
Note: Inside your VmFactory you can also use
isWaiting: isWaiting(MyAction). See
the <a href="https://github.com/marcglasberg/async_redux/blob/master/example/lib/main_show_spinner.dart">
Show Spinner Example</a>.
You can now use var isFailed = context.isFailed(MyAction) to check if an
action of the given type has thrown an UserException. You can then use this
boolean to show an error message.
You can also get the exception with
var exception = context.exceptionFor(MyAction) to
use its error message, and clear the exception with
context.clearExceptionFor(MyAction).
Note: Inside your VmFactory you can also use isFailed: isFailed(MyAction)
etc. See
the <a href="https://github.com/marcglasberg/async_redux/blob/master/example/lib/main_show_error_dialog.dart">
Show Error Dialog Example</a>.
You can add mixins to your actions, to accomplish common tasks:
CheckInternet ensures actions only run with internet, otherwise an error
dialog
prompts users to check their connection:
class LoadText extends ReduxAction<AppState> with CheckInternet {
Future<String> reduce() async {
var response = await http.get('http://numbersapi.com/42');
...
}}
NoDialog can be added to CheckInternet so that no dialog is opened.
Instead, you can display some information in your widgets:
class LoadText extends Action with CheckInternet, NoDialog { ... }
if (context.isFailed(LoadText)) Text('No Internet connection');
AbortWhenNoInternet aborts the action silently (without showing any
dialogs)
if there is no internet connection.
NonReentrant prevents reentrant actions, so that when you dispatch an
action
that's already running it gets aborted (no errors are shown).
Retry retries the action a few times with exponential backoff, if it
fails.
Add UnlimitedRetries to retry the action indefinitely:
class LoadText extends ReduxAction<AppState> with Retry, UnlimitedRetries, NonReentrant {
Other mixins will be provided in the future, for Throttling, Debouncing and Caching.
Some features of the async_redux package are now available in a standalone
Dart-only core package: https://pub.dev/packages/async_redux_core. You may use
that core package when you are developing a Dart server (backend)
with Celest, or when developing your own Dart-only
package that does not depend on Flutter.
Note: For the moment, the core package simply contains the UserException,
and nothing else.
If you now import async_redux_core in your Celest server code and throw an
UserException there, the exception message will automatically be shown in a
dialog to the user in your client app (if you use the UserExceptionDialog
feature).
For Flutter applications nothing changes. You don't need to import the core package directly. You should continue to use this async_redux package, which already exports the code that's now in the core package.
You can now access the store inside of widgets, and have your widgets rebuild
when the state changes, by using context.state and context.dispatch etc.
This is only useful when you want to access the store state, and dispatch
actions directly inside your widgets, instead of using the StoreConnector (dumb
widget / smart widget pattern). For example:
// Read state (will rebuild when the state changes)
var myInfo = context.state.myInfo;
// Dispatch action
context.dispatch(MyAction());
// Use isWaiting to show a spinner
if (context.isWaiting(MyAction)) return CircularProgressIndicator();
// Use isFailed to show an error message
if (context.isFailed(MyAction)) return Text('Loading failed');
// Use exceptionFor to get the error message from the exception
if (context.isFailed(MyAction)) return Text(context.exceptionFor(MyAction).message);
// Use clearExceptionFor to clear the error
context.clearExceptionFor(MyAction);
However, to use context.state as shown above, you need to define the
following extension method in your own code (assuming your state class is
called AppState):
extension BuildContextExtension on BuildContext {
AppState get state => getState<AppState>();
}
See the: <a href="https://github.com/marcglasberg/async_redux/blob/master/example/lib/store_connector_examples/main_extension_vs_store_connector.dart"> Connector vs Provider Example</a>.
You can now get and set properties in the Store using the prop and
setProp methods.
These methods are available in Store, in ReduxAction, and in VmFactory.
They can be used to save global values, but scoped to the store.
For example, you could save timers, streams or futures used by actions:
setProp("timer", Timer(Duration(seconds: 1), () => print("tick")));
var timer = prop<Timer>("timer");
timer.cancel();
You can later use store.disposeProps to stop, close or ignore, all stream
related objects, timers and futures, saved as props in the store. It will also
remove them from there.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
BREAKING: StoreConnector.model was removed, after being deprecated for a long time. Please, use the vm parameter instead. See classes VmFactory and Vm…
BREAKING: StoreConnector.model was removed, after being deprecated
for a long time. Please, use the vm parameter instead. See classes
VmFactory and Vm.
BREAKING: ReduxAction.reduceWithState() was removed, after being
deprecated for a long time.
BREAKING: StoreProvider.of was removed. See context.state and
context.dispatch etc, in version 22.1.0 above.
BREAKING: The UserException class was modified so that it was
possible to move it to the async_redux_core. If your use of UserException
was limited to specifying the error message, then you don't need to change
anything:
throw UserException('Error message') will continue to work as before.
However, for other more advanced features you will have to read the
UserException
documentation and adapt. In the new public API of UserException you can now
specify a message, reason, code, errorText and ifOpenDialog in the
constructor, and then you can use methods addCallbacks, addCause,
addProps,
withErrorText and noDialog to add more information:
throw UserException('Invalid number', reason: 'Must be less than 42')
.addCallbacks(onOk: () => print('OK'), onCancel: () => print('CANCEL'))
.addCause(FormatException('Invalid input'))
.addProps({'number': 42}))
.withErrorText('Type a smaller number')
.noDialog;
Note the code parameter can only be a number now. If you were using a
different type,
for example enums, you can now include it in the props, like
so: throw UserException('').addProps({'code': myError.invalidInput}). or you
can even
create an extension method which allows you to
write throw UserException('').withCode(myError.invalidInput).
However, please read the new UserException documentation to learn about the
recommended way to use code to define the text of the error messages, and
even easily
translate them to the user language by using
the i18n_extension translations
package.
To test the view-model generated by a VmFactory, you can now use the static
method Vm.createFrom(store, factory). The method will return the view-model,
which you
can use to inspect the view-model properties directly, or call any of the
view-model
callbacks. Example:
var store = Store(initialState: User("Mary"));
var vm = Vm.createFrom(store, MyFactory());
// Checking a view-model property.
expect(vm.user.name, "Mary");
// Calling a view-model callback and waiting for the action to finish.
vm.onChangeNameTo("Bill"); // Dispatches SetNameAction("Bill").
await store.waitActionType(SetNameAction);
expect(store.state.name, "Bill");
// Calling a view-model callback and waiting for the state to change.
vm.onChangeNameTo("Bill"); // Dispatches SetNameAction("Bill").
await store.waitCondition((state) => state.name == "Bill");
expect(store.state.name, "Bill");
DEPRECATION WARNING: While the StoreTester is a powerful tool with advanced
features
that are beneficial for the most complex testing scenarios, for almost all
tests
it's now recommended to use the Store directly. This approach 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
new
methods store.dispatchAndWait, store.waitCondition,
store.waitActionCondition,
store.waitAllActions, store.waitActionType, store.waitAllActionTypes,
and store.waitAnyActionTypeFinishes. For example:
// Wait for some action to dispatch and check the state.
await store.dispatchAndWait(MyAction());
expect(store.state.name, 'John')
// Wait for some action to dispatch, and check for errors in the action status.
var status = await dispatchAndWait(MyAction());
expect(status.originalError, isA<UserException>());
// Dispatches two actions in SERIES (one after the other).
await dispatchAndWait(SomeAsyncAction());
await dispatchAndWait(AnotherAsyncAction());
// 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 two actions in PARALLEL and wait for them.
let action1 = BuyAction('IBM');
let action2 = BuyAction('TSLA');
dispatch(action1);
dispatch(action2);
await store.waitAllActions([action1, action2]);
expect(store.state.portfolio.containsAll('IBM', 'TSLA'), isFalse);
// Wait until no actions are in progress.
dispatch(BuyStock('IBM'));
dispatch(BuyStock('TSLA'));
await waitAllActions([]);
expect(state.stocks, ['IBM', 'TSLA']);
// Wait for some action of a given type.
dispatch(ChangeNameAction());
var action = store.waitActionType(ChangeNameAction);
expect(action, isA<ChangeNameAction>());
expect(action.status.isCompleteOk, isTrue);
expect(store.state.name, 'Bill');
// Wait until any action of the given types finishes dispatching.
dispatch(BuyOrSellAction());
var action = store.waitAnyActionTypeFinishes([BuyAction, SellAction]);
expect(store.state.portfolio.contains('IBM'), isTrue);
// Wait for some state condition.
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');
Note the StoreTester will NOT be removed, now or in the future. It's just
not the
recommended
way to test the store anymore.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Replace action.isFinished with action.status.isCompletedOk
DEPRECATION WARNING:
action.isFinished with action.status.isCompletedOkaction.status.isBeforeDone with
action.status.hasFinishedMethodBeforeaction.status.isReduceDone with
action.status.hasFinishedMethodReduceaction.status.isAfterDone with
action.status.hasFinishedMethodAfteraction.status.isFinished with action.status.isCompletedOkThe action.status now has a few more values:
isCompleted if the action has completed executing, either with or
without errors.isCompletedOk if the action has completed with no errors.isCompletedFailed if the action has completed with errors.originalError Holds the error thrown by the action's before/reduce
methods, if
any.wrappedError Holds the error thrown by the action, after it was
processed by the
action's wrapError and the globalWrapError.Nothing published for this version
DEPRECATION WARNING: The wrapError parameter of the Store constructor is now deprecated in favor of the globalWrapError parameter. The reason for this…
DEPRECATION WARNING: The wrapError parameter of the Store constructor is
now
deprecated in
favor of the globalWrapError parameter. The reason for this deprecation is
that the
new GlobalWrapError works in the same way as the action's
ReduxAction.wrapError,
while WrapError does not. The difference is that when WrapError returns
null, the
original
error is not modified, while with GlobalWrapError returning null will
instead
disable the
error. In other words, where your old WrapError returned null, your new
GlobalWrapError
should return the original error:
// WrapError (deprecated):
Object? wrap(error, stackTrace, action) {
if (error is MyException) return null; // Keep the error unaltered.
else return processError(error);
}
// GlobalWrapError:
Object? wrap( error, stackTrace, action) {
if (error is MyException) return error; // Keep the error unaltered.
else return processError(error);
}
Also note, GlobalWrapError is more powerful because it can disable the
error,
whereas WrapError cannot.
Throwing an error in the action's wrapError or in the GlobalWrapError was
disallowed (you needed to make sure it never happened). Now, it's allowed. If instead of
RETURNING
an error
you THROW an error inside these wrappers, AsyncRedux will catch it and use it
instead
the original
error. In other words, returning an error or throwing an error from inside the
wrappers
now has
the same effect. However, it is still recommended to return the error rather
than
throwing it.
DEPRECATION WARNING: Method dispatchAsync was renamed to dispatchAndWait. The old name is still available, but deprecated and will be removed. The new…
dispatchAsync was renamed to dispatchAndWait.
The old
name is
still available, but deprecated and will be removed. The new name is more
descriptive of
what the
method does, and the fact that dispatchAndWait can be used to dispatch both
sync and
async
actions. The only difference between dispatchAndWait and dispatch is that
dispatchAndWait
returns a Future which can be awaited to know when the action is finished.Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
await StoreTester.dispatchAndWait(action) dispatches an action, and then waits until it finishes. This is the same as doing: storeTester.dispatch(acti
await StoreTester.dispatchAndWait(action) dispatches an action, and then
waits until
it
finishes. This is the same as
doing: storeTester.dispatch(action); await storeTester.wait(action);.Nothing published for this version
* Flutter 3.16.0 compatible.
Nothing published for this version
Nothing published for this version
Fixed WrapReduce (which may be used to wrap the reducer to allow for some pre- or post-processing) to avoid async reducers to be called twice.
WrapReduce (which may be used to wrap the reducer to allow for some
pre- or
post-processing) to avoid async reducers to be called twice.Nothing published for this version
* Flutter 3.10.0 and Dart 3.0.0
* Docs improvement.
Your coding agent can read these notes before it upgrades. Set up the MCP server →