asyncredux-navigation
Handle navigation through actions using NavigateAction. Covers setting up the navigator key, dispatching NavigateAction for push/pop/replace, and testing navigation in isolation.
Best use case
asyncredux-navigation is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Handle navigation through actions using NavigateAction. Covers setting up the navigator key, dispatching NavigateAction for push/pop/replace, and testing navigation in isolation.
Teams using asyncredux-navigation should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/asyncredux-navigation/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How asyncredux-navigation Compares
| Feature / Agent | asyncredux-navigation | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Handle navigation through actions using NavigateAction. Covers setting up the navigator key, dispatching NavigateAction for push/pop/replace, and testing navigation in isolation.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Navigation with NavigateAction
AsyncRedux enables app navigation through action dispatching, making it easier to unit test navigation logic. This approach is optional and currently supports Navigator 1 only.
## Setup
### 1. Create and Register the Navigator Key
Create a global navigator key and register it with NavigateAction during app initialization:
```dart
import 'package:async_redux/async_redux.dart';
import 'package:flutter/material.dart';
final navigatorKey = GlobalKey<NavigatorState>();
void main() async {
NavigateAction.setNavigatorKey(navigatorKey);
// ... rest of initialization
runApp(MyApp());
}
```
### 2. Configure MaterialApp
Pass the same navigator key to your MaterialApp:
```dart
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
routes: {
'/': (context) => HomePage(),
'/details': (context) => DetailsPage(),
'/settings': (context) => SettingsPage(),
},
navigatorKey: navigatorKey,
),
);
}
}
```
## Dispatching Navigation Actions
### Push Operations
```dart
// Push a named route
dispatch(NavigateAction.pushNamed('/details'));
// Push a route with a Route object
dispatch(NavigateAction.push(
MaterialPageRoute(builder: (context) => DetailsPage()),
));
// Push and replace current route (named)
dispatch(NavigateAction.pushReplacementNamed('/newRoute'));
// Push and replace current route (with Route object)
dispatch(NavigateAction.pushReplacement(
MaterialPageRoute(builder: (context) => NewPage()),
));
// Pop current route and push a new named route
dispatch(NavigateAction.popAndPushNamed('/otherRoute'));
// Push named route and remove all routes until predicate is true
dispatch(NavigateAction.pushNamedAndRemoveUntil(
'/home',
(route) => false, // Removes all routes
));
// Push named route and remove all routes (convenience method)
dispatch(NavigateAction.pushNamedAndRemoveAll('/home'));
// Push route and remove until predicate
dispatch(NavigateAction.pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => HomePage()),
(route) => false,
));
```
### Pop Operations
```dart
// Pop the current route
dispatch(NavigateAction.pop());
// Pop with a result value
dispatch(NavigateAction.pop(result: 'some_value'));
// Pop routes until predicate is true
dispatch(NavigateAction.popUntil((route) => route.isFirst));
// Pop until reaching a specific named route
dispatch(NavigateAction.popUntilRouteName('/home'));
// Pop until reaching a specific route
dispatch(NavigateAction.popUntilRoute(someRoute));
```
### Replace Operations
```dart
// Replace a specific route with a new one
dispatch(NavigateAction.replace(
oldRoute: currentRoute,
newRoute: MaterialPageRoute(builder: (context) => NewPage()),
));
// Replace the route below the current one
dispatch(NavigateAction.replaceRouteBelow(
anchorRoute: currentRoute,
newRoute: MaterialPageRoute(builder: (context) => NewPage()),
));
```
### Remove Operations
```dart
// Remove a specific route
dispatch(NavigateAction.removeRoute(routeToRemove));
// Remove the route below a specific route
dispatch(NavigateAction.removeRouteBelow(anchorRoute));
```
## Complete Example
```dart
import 'package:async_redux/async_redux.dart';
import 'package:flutter/material.dart';
late Store<AppState> store;
final navigatorKey = GlobalKey<NavigatorState>();
void main() async {
NavigateAction.setNavigatorKey(navigatorKey);
store = Store<AppState>(initialState: AppState());
runApp(MyApp());
}
class AppState {}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
routes: {
'/': (context) => HomePage(),
'/details': (context) => DetailsPage(),
},
navigatorKey: navigatorKey,
),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
child: Text('Go to Details'),
onPressed: () => context.dispatch(NavigateAction.pushNamed('/details')),
),
),
);
}
}
class DetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details')),
body: Center(
child: ElevatedButton(
child: Text('Go Back'),
onPressed: () => context.dispatch(NavigateAction.pop()),
),
),
);
}
}
```
## Getting the Current Route Name
Rather than storing the current route in your app state (which can create complications), access it directly:
```dart
String routeName = NavigateAction.getCurrentNavigatorRouteName(context);
```
## Navigation from Actions
You can dispatch navigation actions from within other actions:
```dart
class LoginAction extends ReduxAction<AppState> {
final String username;
final String password;
LoginAction({required this.username, required this.password});
@override
Future<AppState?> reduce() async {
final user = await api.login(username, password);
// Navigate to home after successful login
dispatch(NavigateAction.pushReplacementNamed('/home'));
return state.copy(user: user);
}
}
```
## Testing Navigation
NavigateAction enables unit testing of navigation without widget or driver tests:
```dart
test('login navigates to home on success', () async {
final store = Store<AppState>(initialState: AppState());
// Capture dispatched actions
NavigateAction? navigateAction;
store.actionObservers.add((action, ini, prevState, newState) {
if (action is NavigateAction) {
navigateAction = action;
}
});
await store.dispatchAndWait(LoginAction(
username: 'test',
password: 'password',
));
// Assert navigation type
expect(navigateAction!.type, NavigateType.pushReplacementNamed);
// Assert route name
expect(
(navigateAction!.details as NavigatorDetails_PushReplacementNamed).routeName,
'/home',
);
});
```
### NavigateType Enum Values
The `NavigateType` enum includes values for all navigation operations:
- `push`, `pushNamed`
- `pop`
- `pushReplacement`, `pushReplacementNamed`
- `popAndPushNamed`
- `pushAndRemoveUntil`, `pushNamedAndRemoveUntil`, `pushNamedAndRemoveAll`
- `popUntil`, `popUntilRouteName`, `popUntilRoute`
- `replace`, `replaceRouteBelow`
- `removeRoute`, `removeRouteBelow`
## Important Notes
- Navigation via AsyncRedux is entirely optional
- Currently supports Navigator 1 only
- For modern navigation packages (like go_router), you'll need to create custom action implementations
- Don't store the current route in your app state; use `getCurrentNavigatorRouteName()` instead
## References
URLs from the documentation:
- https://asyncredux.com/flutter/miscellaneous/navigation
- https://asyncredux.com/flutter/testing/testing-navigation
- https://github.com/marcglasberg/async_redux/blob/master/example/lib/main_navigate.dart
- https://raw.githubusercontent.com/marcglasberg/async_redux/master/lib/src/navigate_action.dartRelated Skills
asyncredux-wait-fail-succeed
Show loading states and handle action failures in widgets. Covers `isWaiting(ActionType)` for spinners, `isFailed(ActionType)` for error states, `exceptionFor(ActionType)` for error messages, and `clearExceptionFor()` to reset failure states.
asyncredux-wait-condition
Use `waitCondition()` inside actions to pause execution until state meets criteria. Covers waiting for price thresholds, coordinating between actions, and implementing conditional workflows.
asyncredux-user-exceptions
Handle user-facing errors with UserException. Covers throwing UserException from actions, setting up UserExceptionDialog, customizing error dialogs with `onShowUserExceptionDialog`, and using UserExceptionAction for non-interrupting error display.
asyncredux-sync-actions
Creates AsyncRedux (Flutter) synchronous actions that update state immediately by implementing reduce() to return a new state.
asyncredux-streams-timers
Manage Streams and Timers with AsyncRedux. Covers creating actions to start/stop streams, storing stream subscriptions in store props, dispatching actions from stream callbacks, and proper cleanup with disposeProps().
asyncredux-state-access
Access store state in widgets using `context.state`, `context.select()`, and `context.read()`. Covers when to use each method, setting up BuildContext extensions, and optimizing widget rebuilds with selective state access.
asyncredux-setup
Initialize, setup and configure AsyncRedux in a Flutter app. Use it whenever starting a new AsyncRedux project, or when the user requests.
asyncredux-selectors
Create and cache selectors for efficient state access. Covers writing selector functions, caching with `cache1` and `cache2`, the reselect pattern, and avoiding repeated computations in widgets.
asyncredux-persistence
Implement local state persistence using Persistor. Covers creating a custom Persistor class, implementing `readState()`, `persistDifference()`, `deleteState()`, using LocalPersist helper, throttling saves, and pausing/resuming persistence with app lifecycle.
asyncredux-optimistic-update-mixin
Add the OptimisticUpdate mixin for instant UI feedback before server confirmation. Covers immediate state changes, automatic rollback on failure, and optionally notifying users of rollback.
asyncredux-events
Use the Event class to interact with Flutter's stateful widgets (TextField, ListView, etc.). Covers creating Event objects in state, consuming events with `context.event()`, scrolling lists, changing text fields, and the event lifecycle.
asyncredux-dependency-injection
Inject dependencies into actions using the environment pattern. Covers creating an Environment class, passing it to the Store, accessing `env` from actions, and using dependency injection for testability.