Flutter Navigator 2.0 + Riverpod 完整路由管理方案

Flutter Navigator 2.0 + Riverpod 完整路由管理方案

结合 Flutter 最流行的状态管理框架 Riverpod,构建声明式、类型安全的路由系统

一、为什么选择 Riverpod 管理路由?

传统方案 vs Riverpod 方案

对比维度 传统方案 Riverpod 方案

状态共享 需要传递回调/使用 InheritedWidget Provider 自动处理依赖关系

类型安全 弱类型,容易出错 强类型,编译时检查

测试难度 需要 Mock BuildContext 可独立测试路由逻辑

热重载 路由状态可能丢失 状态持久化,热重载友好

代码组织 逻辑分散在多个类中 集中管理,清晰分层

核心优势

  1. 依赖注入:自动解析路由依赖(如认证服务)
  2. 响应式编程:路由状态变化自动触发 UI 更新
  3. 作用域管理:不同的路由作用域可以有不同的配置
  4. 组合能力:可以轻松组合多个路由 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 性能优化建议

  1. 使用 AutoDisposeProvider:
dart 复制代码
final productProvider = FutureProvider.autoDispose
.family<Product, String>((ref, id) async {
  return await fetchProduct(id);
});
  1. 页面缓存策略:
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();
  }
}
  1. 延迟加载路由:
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 路由,我们获得了一个:

  1. 类型安全的路由系统
  2. 响应式的状态管理
  3. 易于测试的架构
  4. 可扩展的中间件机制
  5. 平台无关的深度链接支持
相关推荐
小白|2 小时前
集成 OpenHarmony Push Kit 到 Flutter:打造跨端统一推送能力的实战指南
flutter
小白|2 小时前
OpenHarmony + Flutter 混合开发深度实践:构建支持国密算法(SM2/SM3/SM4)与安全存储的金融级应用
算法·安全·flutter
500842 小时前
鸿蒙 Flutter 接入鸿蒙系统能力:通知(本地 / 推送)与后台任务
java·flutter·华为·性能优化·架构
帅气马战的账号2 小时前
开源鸿蒙Flutter原生增强组件:7类高频场景解决方案,极致轻量+深度适配
flutter
ujainu2 小时前
Flutter与DevEco Studio协同开发:轻量化实战指南
flutter
小白|3 小时前
OpenHarmony + Flutter 混合开发实战:深度集成 AI Kit 实现端侧图像识别与智能分析
人工智能·flutter
松☆3 小时前
OpenHarmony + Flutter 混合开发实战:构建支持多模态输入(语音+手势+触控)的智能交互应用
flutter·交互·xcode
解局易否结局3 小时前
鸿蒙版GitCode口袋工具开发:两个问题与解决方法
flutter
吃好喝好玩好睡好3 小时前
OpenHarmony 跨端开发实战:Electron 与 Flutter 的深度融合与性能优化
flutter·性能优化·electron