Flutter Navigator 2.0 + Riverpod 完整路由管理方案
结合 Flutter 最流行的状态管理框架 Riverpod,构建声明式、类型安全的路由系统
一、为什么选择 Riverpod 管理路由?
传统方案 vs Riverpod 方案
对比维度 传统方案 Riverpod 方案
状态共享 需要传递回调/使用 InheritedWidget Provider 自动处理依赖关系
类型安全 弱类型,容易出错 强类型,编译时检查
测试难度 需要 Mock BuildContext 可独立测试路由逻辑
热重载 路由状态可能丢失 状态持久化,热重载友好
代码组织 逻辑分散在多个类中 集中管理,清晰分层
核心优势
- 依赖注入:自动解析路由依赖(如认证服务)
- 响应式编程:路由状态变化自动触发 UI 更新
- 作用域管理:不同的路由作用域可以有不同的配置
- 组合能力:可以轻松组合多个路由 Provider
二、基础架构:从零搭建 Riverpod 路由系统
2.1 项目初始化
yaml
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.0.0
riverpod_annotation: ^2.0.0
freezed_annotation: ^2.0.0
dev_dependencies:
build_runner:
riverpod_generator: ^2.0.0
freezed: ^2.0.0
2.2 基础路由架构
dart
// lib/routing/router.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'router.freezed.dart';
/// 1. 定义所有路由路径(使用 Freezed 实现值类型)
@freezed
class RoutePath with _$RoutePath {
const factory RoutePath.home() = HomeRoute;
const factory RoutePath.productDetail({
required String productId,
@Default(false) bool editMode,
}) = ProductDetailRoute;
const factory RoutePath.settings() = SettingsRoute;
const factory RoutePath.login({
String? redirectTo,
}) = LoginRoute;
const factory RoutePath.profile({
required String userId,
}) = ProfileRoute;
const factory RoutePath.unknown() = UnknownRoute;
factory RoutePath.fromUri(Uri uri) {
final segments = uri.pathSegments;
if (segments.isEmpty) return const RoutePath.home();
switch (segments[0]) {
case 'product':
if (segments.length > 1) {
return RoutePath.productDetail(
productId: segments[1],
editMode: uri.queryParameters['edit'] == 'true',
);
}
break;
case 'settings':
return const RoutePath.settings();
case 'login':
return RoutePath.login(
redirectTo: uri.queryParameters['redirect'],
);
case 'profile':
if (segments.length > 1) {
return RoutePath.profile(userId: segments[1]);
}
break;
}
return const RoutePath.unknown();
}
}
extension RoutePathExtension on RoutePath {
String toUri() {
return when(
home: () => '/',
productDetail: (productId, editMode) {
final base = '/product/$productId';
return editMode ? '$base?edit=true' : base;
},
settings: () => '/settings',
login: (redirectTo) {
return redirectTo != null
? '/login?redirect=$redirectTo'
: '/login';
},
profile: (userId) => '/profile/$userId',
unknown: () => '/404',
);
}
String get pageTitle {
return when(
home: () => '首页',
productDetail: (productId, _) => '商品详情',
settings: () => '设置',
login: (_) => '登录',
profile: (_) => '个人资料',
unknown: () => '页面未找到',
);
}
}
/// 2. 路由状态类
@freezed
class RouterState with _$RouterState {
const factory RouterState({
required List<RoutePath> stack,
@Default(false) bool isLoading,
Object? error,
}) = _RouterState;
factory RouterState.initial() => RouterState(
stack: [const RoutePath.home()],
);
const RouterState._();
RoutePath get current => stack.last;
RouterState push(RoutePath route) {
return copyWith(stack: [...stack, route]);
}
RouterState pop() {
if (stack.length > 1) {
return copyWith(stack: stack.sublist(0, stack.length - 1));
}
return this;
}
RouterState replace(RoutePath route) {
return copyWith(stack: [...stack.sublist(0, stack.length - 1), route]);
}
RouterState popUntil(RoutePath route) {
final index = stack.lastIndexWhere((r) => r == route);
if (index != -1) {
return copyWith(stack: stack.sublist(0, index + 1));
}
return this;
}
RouterState clearAndPush(RoutePath route) {
return copyWith(stack: [route]);
}
}
/// 3. 路由 Provider
final routerProvider = NotifierProvider<RouterNotifier, RouterState>(
RouterNotifier.new,
);
class RouterNotifier extends Notifier<RouterState> {
@override
RouterState build() {
return RouterState.initial();
}
// 导航方法
void push(RoutePath route) {
state = state.push(route);
}
void pop([dynamic result]) {
state = state.pop();
}
void replace(RoutePath route) {
state = state.replace(route);
}
void popUntil(RoutePath route) {
state = state.popUntil(route);
}
void clearAndPush(RoutePath route) {
state = state.clearAndPush(route);
}
// 处理深度链接
void handleDeepLink(String url) {
final uri = Uri.parse(url);
final route = RoutePath.fromUri(uri);
clearAndPush(route);
}
}
/// 4. 路由委托
final routerDelegateProvider = Provider<AppRouterDelegate>((ref) {
return AppRouterDelegate(ref);
});
class AppRouterDelegate extends RouterDelegate<RoutePath>
with PopNavigatorRouterDelegateMixin<RoutePath> {
final Ref _ref;
late final GlobalKey<NavigatorState> navigatorKey;
AppRouterDelegate(this._ref) {
navigatorKey = GlobalKey<NavigatorState>();
}
@override
RoutePath get currentConfiguration => _ref.read(routerProvider).current;
@override
Widget build(BuildContext context) {
final state = _ref.watch(routerProvider);
return Navigator(
key: navigatorKey,
pages: _buildPages(state),
onPopPage: (route, result) {
if (!route.didPop(result)) return false;
_ref.read(routerProvider.notifier).pop(result);
return true;
},
);
}
List<Page> _buildPages(RouterState state) {
final pages = <Page>[];
for (final route in state.stack) {
pages.add(_createPage(route));
}
return pages;
}
Page _createPage(RoutePath route) {
return MaterialPage(
key: ValueKey(route.toUri()),
child: route.when(
home: () => HomePage(),
productDetail: (productId, editMode) => ProductDetailPage(
productId: productId,
editMode: editMode,
),
settings: () => SettingsPage(),
login: (redirectTo) => LoginPage(redirectTo: redirectTo),
profile: (userId) => ProfilePage(userId: userId),
unknown: () => NotFoundPage(),
),
);
}
@override
Future<void> setNewRouteConfiguration(RoutePath configuration) async {
_ref.read(routerProvider.notifier).clearAndPush(configuration);
}
}
/// 5. 路由解析器
final routeInformationParserProvider = Provider<AppRouteInformationParser>(
(ref) => AppRouteInformationParser(),
);
class AppRouteInformationParser extends RouteInformationParser<RoutePath> {
@override
Future<RoutePath> parseRouteInformation(
RouteInformation routeInformation,
) async {
final uri = Uri.parse(routeInformation.location ?? '/');
return RoutePath.fromUri(uri);
}
@override
RouteInformation? restoreRouteInformation(RoutePath configuration) {
return RouteInformation(location: configuration.toUri());
}
}
2.3 应用入口
dart
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'routing/router.dart';
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final routerDelegate = ref.watch(routerDelegateProvider);
final routeInformationParser = ref.watch(routeInformationParserProvider);
return MaterialApp.router(
title: 'Riverpod Router Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
routerDelegate: routerDelegate,
routeInformationParser: routeInformationParser,
backButtonDispatcher: RootBackButtonDispatcher(),
);
}
}
三、进阶功能:权限控制与路由守卫
3.1 认证服务集成
dart
// lib/services/auth_service.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'auth_service.freezed.dart';
/// 用户模型
@freezed
class AppUser with _$AppUser {
const factory AppUser({
required String id,
required String email,
required String name,
required List<String> roles,
@Default(false) bool isVerified,
DateTime? lastLogin,
}) = _AppUser;
const AppUser._();
bool get isAdmin => roles.contains('admin');
bool get isModerator => roles.contains('moderator');
}
/// 认证状态
@freezed
class AuthState with _$AuthState {
const factory AuthState.initial() = AuthInitial;
const factory AuthState.loading() = AuthLoading;
const factory AuthState.authenticated(AppUser user) = AuthAuthenticated;
const factory AuthState.unauthenticated(String? error) = AuthUnauthenticated;
}
/// 认证服务 Provider
final authServiceProvider = NotifierProvider<AuthService, AuthState>(
AuthService.new,
);
class AuthService extends Notifier<AuthState> {
@override
AuthState build() {
// 初始化时尝试恢复登录状态
_tryRestoreSession();
return const AuthState.initial();
}
Future<void> login(String email, String password) async {
state = const AuthState.loading();
try {
// 模拟网络请求
await Future.delayed(Duration(seconds: 1));
// 根据邮箱判断角色
final roles = email.contains('admin')
? ['user', 'admin']
: ['user'];
final user = AppUser(
id: '1',
email: email,
name: email.split('@').first,
roles: roles,
isVerified: true,
lastLogin: DateTime.now(),
);
// 保存到本地存储
await _saveSession(user);
state = AuthState.authenticated(user);
} catch (e) {
state = AuthState.unauthenticated(e.toString());
}
}
Future<void> logout() async {
await _clearSession();
state = const AuthState.unauthenticated(null);
}
Future<void> _tryRestoreSession() async {
// 从本地存储恢复会话
// 实现略...
}
Future<void> _saveSession(AppUser user) async {
// 保存到本地存储
// 实现略...
}
Future<void> _clearSession() async {
// 清除本地存储
// 实现略...
}
}
/// 便捷 Provider
final currentUserProvider = Provider<AppUser?>((ref) {
final authState = ref.watch(authServiceProvider);
return authState.maybeWhen(
authenticated: (user) => user,
orElse: () => null,
);
});
final isAuthenticatedProvider = Provider<bool>((ref) {
return ref.watch(authServiceProvider) is AuthAuthenticated;
});
final userRolesProvider = Provider<List<String>>((ref) {
final user = ref.watch(currentUserProvider);
return user?.roles ?? [];
});
3.2 路由守卫中间件
dart
// lib/routing/middleware.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'router.dart';
import '../services/auth_service.dart';
/// 路由守卫类型
enum RouteGuard {
none,
authRequired,
guestOnly,
adminOnly,
verifiedOnly,
}
/// 路由元数据
class RouteMetadata {
final RouteGuard guard;
final String? title;
final bool keepAlive;
const RouteMetadata({
this.guard = RouteGuard.none,
this.title,
this.keepAlive = false,
});
}
/// 路由守卫映射
final Map<Type, RouteMetadata> _routeGuards = {
HomeRoute: const RouteMetadata(title: '首页'),
SettingsRoute: RouteMetadata(
guard: RouteGuard.authRequired,
title: '设置',
),
LoginRoute: RouteMetadata(
guard: RouteGuard.guestOnly,
title: '登录',
),
ProfileRoute: RouteMetadata(
guard: RouteGuard.authRequired,
title: '个人资料',
),
};
/// 路由守卫检查器
class RouteGuardChecker {
final WidgetRef ref;
RouteGuardChecker(this.ref);
Future<RouteGuardResult> check(RoutePath route) {
final metadata = _routeGuards[route.runtimeType] ?? const RouteMetadata();
switch (metadata.guard) {
case RouteGuard.none:
return Future.value(RouteGuardResult.allowed());
case RouteGuard.authRequired:
return _checkAuthRequired(route);
case RouteGuard.guestOnly:
return _checkGuestOnly(route);
case RouteGuard.adminOnly:
return _checkAdminOnly(route);
case RouteGuard.verifiedOnly:
return _checkVerifiedOnly(route);
}
}
Future<RouteGuardResult> _checkAuthRequired(RoutePath route) async {
final isAuthenticated = ref.read(isAuthenticatedProvider);
if (!isAuthenticated) {
return RouteGuardResult.redirect(
RoutePath.login(redirectTo: route.toUri()),
);
}
return RouteGuardResult.allowed();
}
Future<RouteGuardResult> _checkGuestOnly(RoutePath route) async {
final isAuthenticated = ref.read(isAuthenticatedProvider);
if (isAuthenticated) {
return RouteGuardResult.redirect(const RoutePath.home());
}
return RouteGuardResult.allowed();
}
Future<RouteGuardResult> _checkAdminOnly(RoutePath route) async {
final user = ref.read(currentUserProvider);
if (user == null) {
return RouteGuardResult.redirect(
RoutePath.login(redirectTo: route.toUri()),
);
}
if (!user.isAdmin) {
return RouteGuardResult.denied('需要管理员权限');
}
return RouteGuardResult.allowed();
}
Future<RouteGuardResult> _checkVerifiedOnly(RoutePath route) async {
final user = ref.read(currentUserProvider);
if (user == null) {
return RouteGuardResult.redirect(
RoutePath.login(redirectTo: route.toUri()),
);
}
if (!user.isVerified) {
return RouteGuardResult.denied('请先验证邮箱');
}
return RouteGuardResult.allowed();
}
}
/// 守卫检查结果
@freezed
class RouteGuardResult with _$RouteGuardResult {
const factory RouteGuardResult.allowed() = RouteGuardAllowed;
const factory RouteGuardResult.redirect(RoutePath route) = RouteGuardRedirect;
const factory RouteGuardResult.denied(String message) = RouteGuardDenied;
}
/// 守卫检查 Provider
final routeGuardCheckerProvider = Provider<RouteGuardChecker>((ref) {
return RouteGuardChecker(ref);
});
/// 增强的路由 Notifier
final guardedRouterProvider = NotifierProvider<GuardedRouterNotifier, RouterState>(
GuardedRouterNotifier.new,
);
class GuardedRouterNotifier extends Notifier<RouterState> {
late final RouteGuardChecker _guardChecker;
@override
RouterState build() {
_guardChecker = ref.read(routeGuardCheckerProvider);
return RouterState.initial();
}
Future<void> push(RoutePath route) async {
final result = await _guardChecker.check(route);
result.when(
allowed: () => state = state.push(route),
redirect: (redirectRoute) => state = state.push(redirectRoute),
denied: (message) => _showError(message),
);
}
Future<void> replace(RoutePath route) async {
final result = await _guardChecker.check(route);
result.when(
allowed: () => state = state.replace(route),
redirect: (redirectRoute) => state = state.replace(redirectRoute),
denied: (message) => _showError(message),
);
}
void _showError(String message) {
// 可以使用 ScaffoldMessenger 显示错误
final context = navigatorKey.currentContext;
if (context != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
}
}
四、页面组件与导航实践
4.1 基础页面组件
dart
// lib/pages/home_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../routing/router.dart';
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.read(routerProvider.notifier);
return Scaffold(
appBar: AppBar(
title: const Text('首页'),
),
body: ListView(
children: [
_buildCard(
context,
title: '商品详情',
subtitle: '查看商品信息',
icon: Icons.shopping_bag,
onTap: () => router.push(
const RoutePath.productDetail(productId: '123'),
),
),
_buildCard(
context,
title: '个人资料',
subtitle: '查看和编辑个人信息',
icon: Icons.person,
onTap: () => router.push(
const RoutePath.profile(userId: 'current'),
),
),
_buildCard(
context,
title: '设置',
subtitle: '应用设置',
icon: Icons.settings,
onTap: () => router.push(const RoutePath.settings()),
),
_buildCard(
context,
title: '需要登录',
subtitle: '测试路由守卫',
icon: Icons.lock,
onTap: () => router.push(
RoutePath.productDetail(
productId: '456',
editMode: true,
),
),
),
],
),
);
}
Widget _buildCard(
BuildContext context, {
required String title,
required String subtitle,
required IconData icon,
required VoidCallback onTap,
}) {
return Card(
margin: const EdgeInsets.all(8),
child: ListTile(
leading: Icon(icon, size: 32),
title: Text(title),
subtitle: Text(subtitle),
trailing: const Icon(Icons.arrow_forward),
onTap: onTap,
),
);
}
}
4.2 带参数的路由页面
dart
// lib/pages/product_detail_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../routing/router.dart';
class ProductDetailPage extends ConsumerWidget {
final String productId;
final bool editMode;
const ProductDetailPage({
super.key,
required this.productId,
required this.editMode,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.read(routerProvider.notifier);
return Scaffold(
appBar: AppBar(
title: Text('商品 $productId'),
actions: [
if (editMode)
IconButton(
icon: const Icon(Icons.save),
onPressed: () {
// 保存逻辑
router.pop(); // 返回上一页
},
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'商品ID: $productId',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 16),
Text(
'编辑模式: ${editMode ? "是" : "否"}',
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 24),
if (!editMode)
ElevatedButton(
onPressed: () {
// 进入编辑模式
router.replace(
RoutePath.productDetail(
productId: productId,
editMode: true,
),
);
},
child: const Text('编辑'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// 模拟网络请求后的返回
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认'),
content: const Text('确定要购买吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
router.pop('购买成功');
},
child: const Text('确认'),
),
],
),
);
},
child: const Text('购买'),
),
],
),
),
);
}
}
4.3 登录页面(带重定向)
dart
// lib/pages/login_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../routing/router.dart';
import '../services/auth_service.dart';
class LoginPage extends ConsumerStatefulWidget {
final String? redirectTo;
const LoginPage({
super.key,
this.redirectTo,
});
@override
ConsumerState<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends ConsumerState<LoginPage> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController(text: 'user@example.com');
final _passwordController = TextEditingController(text: 'password');
bool _isLoading = false;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final router = ref.read(routerProvider.notifier);
return Scaffold(
appBar: AppBar(
title: const Text('登录'),
),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: '邮箱',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入邮箱';
}
if (!value.contains('@')) {
return '邮箱格式不正确';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _passwordController,
decoration: const InputDecoration(
labelText: '密码',
border: OutlineInputBorder(),
),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
if (value.length < 6) {
return '密码至少6位';
}
return null;
},
),
const SizedBox(height: 24),
if (_isLoading)
const CircularProgressIndicator()
else
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _login,
child: const Text('登录'),
),
),
const SizedBox(height: 16),
TextButton(
onPressed: () {
// 测试用户快速登录
_emailController.text = 'admin@example.com';
_passwordController.text = 'password';
},
child: const Text('使用测试账号'),
),
],
),
),
),
);
}
Future<void> _login() async {
if (_formKey.currentState?.validate() ?? false) {
setState(() => _isLoading = true);
try {
await ref.read(authServiceProvider.notifier).login(
_emailController.text,
_passwordController.text,
);
// 登录成功后处理重定向
final redirectTo = widget.redirectTo;
final router = ref.read(routerProvider.notifier);
if (redirectTo != null) {
// 解析重定向路径
final uri = Uri.parse(redirectTo);
final route = RoutePath.fromUri(uri);
router.replace(route);
} else {
router.pop(); // 返回上一页
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('登录失败: $e')),
);
} finally {
setState(() => _isLoading = false);
}
}
}
}
五、高级特性与优化
5.1 嵌套导航(BottomNavigationBar)
dart
// lib/routing/nested_router.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'router.dart';
/// 标签页路由配置
@freezed
class TabRoutePath with _$TabRoutePath {
const factory TabRoutePath.home() = TabHomeRoute;
const factory TabRoutePath.explore() = TabExploreRoute;
const factory TabRoutePath.cart() = TabCartRoute;
const factory TabRoutePath.profile() = TabProfileRoute;
}
/// 标签页状态
@freezed
class TabRouterState with _$TabRouterState {
const factory TabRouterState({
required TabRoutePath currentTab,
required Map<TabRoutePath, List<RoutePath>> tabStacks,
}) = _TabRouterState;
factory TabRouterState.initial() => TabRouterState(
currentTab: const TabRoutePath.home(),
tabStacks: {
const TabRoutePath.home(): [const RoutePath.home()],
const TabRoutePath.explore(): [const RoutePath.home()],
const TabRoutePath.cart(): [const RoutePath.home()],
const TabRoutePath.profile(): [const RoutePath.home()],
},
);
}
/// 标签页路由 Provider
final tabRouterProvider = NotifierProvider<TabRouterNotifier, TabRouterState>(
TabRouterNotifier.new,
);
class TabRouterNotifier extends Notifier<TabRouterState> {
@override
TabRouterState build() {
return TabRouterState.initial();
}
void switchTab(TabRoutePath tab) {
state = state.copyWith(currentTab: tab);
}
void pushToTab(RoutePath route) {
final tab = state.currentTab;
final currentStack = state.tabStacks[tab] ?? [];
final newStacks = Map<TabRoutePath, List<RoutePath>>.from(state.tabStacks);
newStacks[tab] = [...currentStack, route];
state = state.copyWith(tabStacks: newStacks);
}
void popFromTab() {
final tab = state.currentTab;
final currentStack = state.tabStacks[tab] ?? [];
if (currentStack.length > 1) {
final newStacks = Map<TabRoutePath, List<RoutePath>>.from(state.tabStacks);
newStacks[tab] = currentStack.sublist(0, currentStack.length - 1);
state = state.copyWith(tabStacks: newStacks);
}
}
RoutePath get currentRoute {
final tab = state.currentTab;
final stack = state.tabStacks[tab] ?? [];
return stack.last;
}
}
/// 主框架组件
class MainTabFrame extends ConsumerWidget {
const MainTabFrame({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabState = ref.watch(tabRouterProvider);
final router = ref.read(routerProvider.notifier);
return Scaffold(
body: IndexedStack(
index: _getTabIndex(tabState.currentTab),
children: [
_buildTabNavigator(ref, const TabRoutePath.home()),
_buildTabNavigator(ref, const TabRoutePath.explore()),
_buildTabNavigator(ref, const TabRoutePath.cart()),
_buildTabNavigator(ref, const TabRoutePath.profile()),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _getTabIndex(tabState.currentTab),
onTap: (index) {
ref.read(tabRouterProvider.notifier).switchTab(
_getTabByIndex(index),
);
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: '首页',
),
BottomNavigationBarItem(
icon: Icon(Icons.explore),
label: '发现',
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
label: '购物车',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: '我的',
),
],
),
);
}
int _getTabIndex(TabRoutePath tab) {
return tab.when(
home: () => 0,
explore: () => 1,
cart: () => 2,
profile: () => 3,
);
}
TabRoutePath _getTabByIndex(int index) {
switch (index) {
case 0: return const TabRoutePath.home();
case 1: return const TabRoutePath.explore();
case 2: return const TabRoutePath.cart();
case 3: return const TabRoutePath.profile();
default: return const TabRoutePath.home();
}
}
Widget _buildTabNavigator(WidgetRef ref, TabRoutePath tab) {
return Navigator(
key: ValueKey(tab),
onPopPage: (route, result) {
if (!route.didPop(result)) return false;
ref.read(tabRouterProvider.notifier).popFromTab();
return true;
},
pages: _buildTabPages(ref, tab),
);
}
List<Page> _buildTabPages(WidgetRef ref, TabRoutePath tab) {
final state = ref.watch(tabRouterProvider);
final stack = state.tabStacks[tab] ?? [];
return stack.map((route) {
return MaterialPage(
key: ValueKey('${tab}_${route.toUri()}'),
child: _buildPageForRoute(route),
);
}).toList();
}
Widget _buildPageForRoute(RoutePath route) {
return route.when(
home: () => HomePage(),
productDetail: (productId, editMode) => ProductDetailPage(
productId: productId,
editMode: editMode,
),
settings: () => SettingsPage(),
login: (redirectTo) => LoginPage(redirectTo: redirectTo),
profile: (userId) => ProfilePage(userId: userId),
unknown: () => NotFoundPage(),
);
}
}
5.2 路由监听与分析
dart
// lib/routing/analytics.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'router.dart';
/// 路由分析服务
final routeAnalyticsProvider = Provider<RouteAnalytics>((ref) {
return RouteAnalytics(ref);
});
class RouteAnalytics {
final Ref _ref;
RouteAnalytics(this._ref) {
// 监听路由变化
_ref.listen<RouterState>(
routerProvider,
(previous, next) {
_onRouteChanged(previous, next);
},
);
}
void _onRouteChanged(RouterState? previous, RouterState next) {
final previousRoute = previous?.current;
final currentRoute = next.current;
// 记录页面访问
_logPageView(currentRoute);
// 记录导航路径
_logNavigation(previousRoute, currentRoute);
// 触发分析事件
_sendAnalyticsEvent(currentRoute);
}
void _logPageView(RoutePath route) {
final pageName = route.toUri();
final timestamp = DateTime.now();
print('📊 页面访问: $pageName at $timestamp');
// 这里可以集成 Firebase Analytics、Amplitude 等
}
void _logNavigation(RoutePath? from, RoutePath to) {
if (from != null) {
print('🧭 导航: ${from.toUri()} → ${to.toUri()}');
}
}
void _sendAnalyticsEvent(RoutePath route) {
route.when(
home: () => _trackEvent('view_home'),
productDetail: (productId, editMode) {
_trackEvent('view_product', {
'product_id': productId,
'edit_mode': editMode,
});
},
settings: () => _trackEvent('view_settings'),
login: (_) => _trackEvent('view_login'),
profile: (_) => _trackEvent('view_profile'),
unknown: () => _trackEvent('view_404'),
);
}
void _trackEvent(String name, [Map<String, dynamic>? params]) {
// 发送到分析平台
print('📈 事件: $name ${params ?? {}}');
}
}
5.3 路由测试
dart
// test/routing/router_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:mocktail/mocktail.dart';
import '../lib/routing/router.dart';
class MockRef extends Mock implements Ref {}
void main() {
group('RouterNotifier', () {
late ProviderContainer container;
late RouterNotifier router;
setUp(() {
container = ProviderContainer();
router = container.read(routerProvider.notifier);
});
tearDown(() {
container.dispose();
});
test('初始状态正确', () {
expect(container.read(routerProvider).stack.length, 1);
expect(
container.read(routerProvider).current,
const RoutePath.home(),
);
});
test('push 添加新路由', () {
final newRoute = const RoutePath.settings();
router.push(newRoute);
expect(container.read(routerProvider).stack.length, 2);
expect(container.read(routerProvider).current, newRoute);
});
test('pop 移除最后的路由', () {
router.push(const RoutePath.settings());
expect(container.read(routerProvider).stack.length, 2);
router.pop();
expect(container.read(routerProvider).stack.length, 1);
expect(
container.read(routerProvider).current,
const RoutePath.home(),
);
});
test('不能弹出最后一个路由', () {
router.pop();
expect(container.read(routerProvider).stack.length, 1);
});
test('replace 替换当前路由', () {
router.push(const RoutePath.settings());
final newRoute = const RoutePath.login();
router.replace(newRoute);
expect(container.read(routerProvider).stack.length, 2);
expect(container.read(routerProvider).current, newRoute);
});
test('clearAndPush 清空栈并添加新路由', () {
// 先添加几个路由
router.push(const RoutePath.settings());
router.push(const RoutePath.login());
expect(container.read(routerProvider).stack.length, 3);
// 清空并添加新路由
final newRoute = const RoutePath.profile(userId: '123');
router.clearAndPush(newRoute);
expect(container.read(routerProvider).stack.length, 1);
expect(container.read(routerProvider).current, newRoute);
});
});
group('RoutePath', () {
test('从 URI 解析', () {
expect(
RoutePath.fromUri(Uri.parse('/')),
const RoutePath.home(),
);
expect(
RoutePath.fromUri(Uri.parse('/product/123')),
const RoutePath.productDetail(productId: '123'),
);
expect(
RoutePath.fromUri(Uri.parse('/product/456?edit=true')),
const RoutePath.productDetail(productId: '456', editMode: true),
);
expect(
RoutePath.fromUri(Uri.parse('/login?redirect=/profile')),
const RoutePath.login(redirectTo: '/profile'),
);
});
test('转换为 URI', () {
expect(
const RoutePath.home().toUri(),
'/',
);
expect(
const RoutePath.productDetail(productId: '123').toUri(),
'/product/123',
);
expect(
const RoutePath.productDetail(productId: '456', editMode: true).toUri(),
'/product/456?edit=true',
);
});
});
}
六、最佳实践总结
6.1 代码组织建议
lib/
├── main.dart
├── app/
│ ├── app.dart
│ └── providers.dart
├── routing/
│ ├── router.dart # 路由配置、状态、Provider
│ ├── middleware.dart # 路由守卫
│ ├── nested_router.dart # 嵌套导航
│ └── analytics.dart # 路由分析
├── services/
│ ├── auth_service.dart # 认证服务
│ └── storage_service.dart # 存储服务
├── pages/
│ ├── home_page.dart
│ ├── product_detail_page.dart
│ ├── login_page.dart
│ └── ...
└── widgets/
└── shared/
6.2 性能优化建议
- 使用 AutoDisposeProvider:
dart
final productProvider = FutureProvider.autoDispose
.family<Product, String>((ref, id) async {
return await fetchProduct(id);
});
- 页面缓存策略:
dart
class CachedPage extends StatefulWidget {
const CachedPage({super.key});
@override
State<CachedPage> createState() => _CachedPageState();
}
class _CachedPageState extends State<CachedPage>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Container();
}
}
- 延迟加载路由:
dart
final lazyRoutesProvider = Provider<Map<String, WidgetBuilder>>((ref) {
return {
'/heavy-page': (context) => FutureBuilder(
future: import('pages/heavy_page.dart'),
builder: (context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
}
return const CircularProgressIndicator();
},
),
};
});
6.3 错误处理
dart
final routerProvider = NotifierProvider<RouterNotifier, RouterState>(
RouterNotifier.new,
);
class RouterNotifier extends Notifier<RouterState> {
@override
RouterState build() {
// 监听全局错误
ref.listenSelf((previous, next) {
if (next.error != null) {
_handleError(next.error!);
}
});
return RouterState.initial();
}
void _handleError(Object error) {
// 显示错误页面或 SnackBar
print('路由错误: $error');
}
void pushWithErrorHandling(RoutePath route) async {
try {
// 执行路由逻辑
state = state.push(route);
} catch (e) {
// 导航到错误页面
state = state.push(const RoutePath.unknown());
// 记录错误
ref.read(errorLoggerProvider).log(e);
}
}
}
七、常见问题与解决方案
Q1: 如何在非 Widget 类中使用路由导航?
dart
// 创建一个全局的导航服务
final navigationServiceProvider = Provider<NavigationService>((ref) {
return NavigationService(ref);
});
class NavigationService {
final Ref _ref;
BuildContext? _context;
NavigationService(this._ref);
void setContext(BuildContext context) {
_context = context;
}
void navigateTo(RoutePath route) {
// 方案1:直接使用 Provider
_ref.read(routerProvider.notifier).push(route);
// 方案2:如果有上下文,可以使用传统导航
// if (_context != null) {
// Navigator.of(_context!).push(...);
// }
}
}
// 在根 Widget 中设置上下文
class RootWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.read(navigationServiceProvider).setContext(context);
});
return MaterialApp.router(...);
}
}
Q2: 如何处理 Web 端刷新后路由状态丢失?
dart
final routerProvider = NotifierProvider<RouterNotifier, RouterState>(
() => RouterNotifier(),
);
class RouterNotifier extends Notifier<RouterState> {
@override
RouterState build() {
// 从本地存储恢复路由状态
final savedState = _loadSavedState();
return savedState ?? RouterState.initial();
}
RouterState? _loadSavedState() {
// 从 shared_preferences 或 hive 恢复
// 实现略...
}
void push(RoutePath route) {
final newState = state.push(route);
state = newState;
_saveState(newState);
}
void _saveState(RouterState state) {
// 保存到本地存储
// 实现略...
}
}
Q3: 如何实现路由动画?
dart
final animatedPagesProvider = Provider<List<Page>>((ref) {
final state = ref.watch(routerProvider);
return state.stack.mapIndexed((index, route) {
return PageRouteBuilder(
key: ValueKey('${route.toUri()}-$index'),
pageBuilder: (_, animation, secondaryAnimation) {
return FadeTransition(
opacity: animation,
child: _buildPageForRoute(route),
);
},
transitionsBuilder: (_, animation, __, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.ease;
final tween = Tween(begin: begin, end: end)
.chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
);
}).toList();
});
结语
通过 Riverpod 管理 Flutter Navigator 2.0 路由,我们获得了一个:
- 类型安全的路由系统
- 响应式的状态管理
- 易于测试的架构
- 可扩展的中间件机制
- 平台无关的深度链接支持