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. 平台无关的深度链接支持
相关推荐
程序员Ctrl喵4 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难6 小时前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡7 小时前
flutter列表中实现置顶动画
flutter
始持7 小时前
第十二讲 风格与主题统一
前端·flutter
始持7 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持7 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜8 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴8 小时前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区9 小时前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎9 小时前
树形选择器组件封装
前端·flutter