name: asyncredux-navigation description: 通过使用NavigateAction处理导航。涵盖设置导航器键、分发NavigateAction进行push/pop/replace,以及隔离测试导航。
使用NavigateAction进行导航
AsyncRedux允许通过分发动作进行应用导航,使导航逻辑更容易单元测试。此方法是可选的,目前仅支持Navigator 1。
设置
1. 创建和注册导航器键
创建一个全局导航器键,并在应用初始化期间用NavigateAction注册它:
import 'package:async_redux/async_redux.dart';
import 'package:flutter/material.dart';
final navigatorKey = GlobalKey<NavigatorState>();
void main() async {
NavigateAction.setNavigatorKey(navigatorKey);
// ... 其余初始化
runApp(MyApp());
}
2. 配置MaterialApp
将相同的导航器键传递给您的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,
),
);
}
}
分发导航动作
Push操作
// 推送一个命名路由
dispatch(NavigateAction.pushNamed('/details'));
// 推送一个路由对象
dispatch(NavigateAction.push(
MaterialPageRoute(builder: (context) => DetailsPage()),
));
// 推送并替换当前路由(命名)
dispatch(NavigateAction.pushReplacementNamed('/newRoute'));
// 推送并替换当前路由(使用路由对象)
dispatch(NavigateAction.pushReplacement(
MaterialPageRoute(builder: (context) => NewPage()),
));
// 弹出当前路由并推送一个新的命名路由
dispatch(NavigateAction.popAndPushNamed('/otherRoute'));
// 推送命名路由并移除所有路由直到谓词为真
dispatch(NavigateAction.pushNamedAndRemoveUntil(
'/home',
(route) => false, // 移除所有路由
));
// 推送命名路由并移除所有路由(便捷方法)
dispatch(NavigateAction.pushNamedAndRemoveAll('/home'));
// 推送路由并移除直到谓词
dispatch(NavigateAction.pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => HomePage()),
(route) => false,
));
Pop操作
// 弹出当前路由
dispatch(NavigateAction.pop());
// 弹出并返回一个结果值
dispatch(NavigateAction.pop(result: 'some_value'));
// 弹出路由直到谓词为真
dispatch(NavigateAction.popUntil((route) => route.isFirst));
// 弹出直到达到特定命名路由
dispatch(NavigateAction.popUntilRouteName('/home'));
// 弹出直到达到特定路由
dispatch(NavigateAction.popUntilRoute(someRoute));
Replace操作
// 用新路由替换特定路由
dispatch(NavigateAction.replace(
oldRoute: currentRoute,
newRoute: MaterialPageRoute(builder: (context) => NewPage()),
));
// 替换当前路由下方的路由
dispatch(NavigateAction.replaceRouteBelow(
anchorRoute: currentRoute,
newRoute: MaterialPageRoute(builder: (context) => NewPage()),
));
Remove操作
// 移除特定路由
dispatch(NavigateAction.removeRoute(routeToRemove));
// 移除特定路由下方的路由
dispatch(NavigateAction.removeRouteBelow(anchorRoute));
完整示例
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('首页')),
body: Center(
child: ElevatedButton(
child: Text('前往详情'),
onPressed: () => context.dispatch(NavigateAction.pushNamed('/details')),
),
),
);
}
}
class DetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('详情')),
body: Center(
child: ElevatedButton(
child: Text('返回'),
onPressed: () => context.dispatch(NavigateAction.pop()),
),
),
);
}
}
获取当前路由名称
无需在应用状态中存储当前路由(这可能会造成复杂性),直接访问:
String routeName = NavigateAction.getCurrentNavigatorRouteName(context);
从动作中导航
您可以从其他动作内部分发导航动作:
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);
// 成功登录后导航到首页
dispatch(NavigateAction.pushReplacementNamed('/home'));
return state.copy(user: user);
}
}
测试导航
NavigateAction允许在不使用小部件或驱动测试的情况下进行导航单元测试:
test('登录成功后导航到首页', () async {
final store = Store<AppState>(initialState: AppState());
// 捕获分发的动作
NavigateAction? navigateAction;
store.actionObservers.add((action, ini, prevState, newState) {
if (action is NavigateAction) {
navigateAction = action;
}
});
await store.dispatchAndWait(LoginAction(
username: 'test',
password: 'password',
));
// 断言导航类型
expect(navigateAction!.type, NavigateType.pushReplacementNamed);
// 断言路由名称
expect(
(navigateAction!.details as NavigatorDetails_PushReplacementNamed).routeName,
'/home',
);
});
NavigateType枚举值
NavigateType枚举包括所有导航操作的值:
push,pushNamedpoppushReplacement,pushReplacementNamedpopAndPushNamedpushAndRemoveUntil,pushNamedAndRemoveUntil,pushNamedAndRemoveAllpopUntil,popUntilRouteName,popUntilRoutereplace,replaceRouteBelowremoveRoute,removeRouteBelow
重要注意事项
- 通过AsyncRedux进行导航完全可选
- 目前仅支持Navigator 1
- 对于现代导航包(如go_router),您需要创建自定义动作实现
- 不要将当前路由存储在应用状态中;使用
getCurrentNavigatorRouteName()代替
参考文献
文档中的URL: