Agent skill
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.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/asyncredux-navigation
SKILL.md
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:
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:
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
// 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
// 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
// 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
// Remove a specific route
dispatch(NavigateAction.removeRoute(routeToRemove));
// Remove the route below a specific route
dispatch(NavigateAction.removeRouteBelow(anchorRoute));
Complete Example
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:
String routeName = NavigateAction.getCurrentNavigatorRouteName(context);
Navigation from Actions
You can dispatch navigation actions from within other actions:
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:
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,pushNamedpoppushReplacement,pushReplacementNamedpopAndPushNamedpushAndRemoveUntil,pushNamedAndRemoveUntil,pushNamedAndRemoveAllpopUntil,popUntilRouteName,popUntilRoutereplace,replaceRouteBelowremoveRoute,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:
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?