Claude-skill-registry 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.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/asyncredux-navigation" ~/.claude/skills/majiayu000-claude-skill-registry-asyncredux-navigation && rm -rf "$T"
skills/data/asyncredux-navigation/SKILL.mdNavigation 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:
,pushpushNamedpop
,pushReplacementpushReplacementNamedpopAndPushNamed
,pushAndRemoveUntil
,pushNamedAndRemoveUntilpushNamedAndRemoveAll
,popUntil
,popUntilRouteNamepopUntilRoute
,replacereplaceRouteBelow
,removeRouteremoveRouteBelow
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
insteadgetCurrentNavigatorRouteName()
References
URLs from the documentation: