context_free_router 0.0.1
context_free_router: ^0.0.1 copied to clipboard
No Context Router
无 BuildContext 的 Flutter 路由解决方案:ContextFreeRouter 插件详解 #
1. 插件概述 #
在 Flutter 应用开发中,路由管理是一个核心问题。传统的 Flutter 导航方法通常依赖于 BuildContext
,这意味着你需要在 Widget 树中获取上下文才能执行导航操作。这种依赖带来了一系列限制,尤其是当你需要在非 UI 层(如业务逻辑层或数据层)进行导航时。
ContextFreeRouter 插件正是为解决这一问题而设计的。它提供了一种不依赖 BuildContext
的路由解决方案,让你可以在应用的任何位置进行导航操作,无论是在 UI 组件内部还是在业务逻辑层。
主要优势 #
- 全局访问:在应用的任何位置都可以进行导航,无需传递
BuildContext
- 关注点分离:将导航逻辑与 UI 逻辑分离,使代码更加清晰
- 拦截器机制:提供强大的拦截器系统,可以在路由切换前执行自定义逻辑
- 监控能力:内置路由监控功能,可以跟踪路由变化和相关数据
- 单例模式:采用单例设计,确保全局只有一个路由实例
2. 核心功能详解 #
拦截器机制 #
ContextFreeRouter 的一个核心特性是其强大的拦截器机制。拦截器允许你在路由切换前执行自定义逻辑,例如身份验证、权限检查等。
拦截器接口
所有拦截器都需要实现 RouteInterceptor
接口:
abstract class RouteInterceptor {
/// 拦截器优先级,数值越高优先级越高
int get priority => 0;
/// 在导航前调用,返回 InterceptorResult 控制导航流程
FutureOr<InterceptorResult> onIntercept(InterceptorContext context);
}
拦截器可以返回三种结果:
InterceptorResult.proceed
:继续导航流程InterceptorResult.redirect
:重定向到另一个路由InterceptorResult.cancel
:取消导航
身份验证拦截器示例
以下是一个身份验证拦截器的实现,它会检查用户是否已登录,如果未登录则重定向到登录页面:
class AuthInterceptor implements RouteInterceptor {
final List<String> protectedRoutes;
final String loginRoute;
final FutureOr<bool> Function() isAuthenticatedCallback;
@override
int get priority => 100; // 高优先级,确保先执行
AuthInterceptor({
required this.protectedRoutes,
required this.loginRoute,
required this.isAuthenticatedCallback,
});
@override
FutureOr<InterceptorResult> onIntercept(InterceptorContext context) async {
final to = context.to;
// 检查路由是否受保护
if (protectedRoutes.any((route) => to.path.startsWith(route))) {
// 检查用户是否已认证
final isAuthenticated = await isAuthenticatedCallback();
if (!isAuthenticated) {
// 重定向到登录页,并传递返回路径
context.redirect(
loginRoute,
params: {'returnTo': to.path},
);
return InterceptorResult.redirect;
}
}
return InterceptorResult.proceed;
}
}
权限检查拦截器示例
以下是一个权限检查拦截器,它会检查用户是否具有访问特定路由所需的权限:
class PermissionInterceptor implements RouteInterceptor {
final Map<String, List<String>> routePermissions;
final String unauthorizedRoute;
final FutureOr<PermissionResult> Function(List<String> permissions) checkPermissionsCallback;
@override
int get priority => 90; // 在身份验证拦截器之后运行
PermissionInterceptor({
required this.routePermissions,
required this.unauthorizedRoute,
required this.checkPermissionsCallback,
});
@override
FutureOr<InterceptorResult> onIntercept(InterceptorContext context) async {
final to = context.to;
// 检查路由是否需要权限
final requiredPermissions = _getRequiredPermissions(to.path);
if (requiredPermissions.isNotEmpty) {
// 检查用户是否具有所需权限
final result = await checkPermissionsCallback(requiredPermissions);
if (result == PermissionResult.denied) {
// 重定向到未授权页面
context.redirect(unauthorizedRoute);
return InterceptorResult.redirect;
}
}
return InterceptorResult.proceed;
}
List<String> _getRequiredPermissions(String path) {
for (final entry in routePermissions.entries) {
if (path.startsWith(entry.key)) {
return entry.value;
}
}
return [];
}
}
添加拦截器 #
你可以在应用初始化时添加拦截器:
final router = ContextFreeRouter.instance as ContextFreeRouterImpl;
// 添加身份验证拦截器
router.addInterceptor(AuthInterceptor(
protectedRoutes: ['/dashboard', '/profile'],
loginRoute: '/login',
isAuthenticatedCallback: () async {
// 检查用户是否已认证的逻辑
return await AuthService.isLoggedIn();
},
));
// 添加权限检查拦截器
router.addInterceptor(PermissionInterceptor(
routePermissions: {
'/profile': ['view_profile'],
'/admin': ['admin_access'],
},
unauthorizedRoute: '/unauthorized',
checkPermissionsCallback: (permissions) async {
// 检查用户是否具有所需权限的逻辑
return await PermissionService.checkPermissions(permissions);
},
));
3. 路由注册与使用 #
路由注册 #
在使用 ContextFreeRouter 之前,你需要先注册路由。路由注册通过 register
方法完成,该方法接受一个路径和一个构建器函数:
void main() {
final router = ContextFreeRouter.instance as ContextFreeRouterImpl;
// 注册路由
router.register('/', (config) => HomePage());
router.register('/login', (config) => LoginPage(returnTo: config.params?['returnTo']));
router.register('/dashboard', (config) => DashboardPage());
router.register('/profile', (config) => ProfilePage());
router.register('/unauthorized', (config) => UnauthorizedPage());
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ContextFreeRouter Demo',
navigatorKey: (ContextFreeRouter.instance).navigatorKey, // 重要:使用路由器的navigatorKey
home: HomePage(),
);
}
}
参数传递 #
ContextFreeRouter 支持通过 params
和 extra
参数传递数据:
// 导航到用户详情页,并传递用户ID
ContextFreeRouter.instance.navigateTo(
'/user-details',
params: {'userId': '123'},
extra: {'showActions': true},
);
// 在目标页面中获取参数
class UserDetailsPage extends StatelessWidget {
final String userId;
final bool showActions;
UserDetailsPage({
required this.userId,
this.showActions = false,
});
factory UserDetailsPage.fromConfig(RouteConfig config) {
return UserDetailsPage(
userId: config.params?['userId'] ?? '',
showActions: (config.extra as Map?)?['showActions'] ?? false,
);
}
@override
Widget build(BuildContext context) {
// 构建UI
}
}
// 注册路由时使用工厂构造函数
router.register('/user-details', (config) => UserDetailsPage.fromConfig(config));
导航操作 #
ContextFreeRouter 提供了简单的导航 API:
// 导航到新页面
ContextFreeRouter.instance.navigateTo('/dashboard');
// 替换当前页面
ContextFreeRouter.instance.navigateTo('/login', replace: true);
// 返回上一页
ContextFreeRouter.instance.goBack();
// 返回上一页并传递结果
ContextFreeRouter.instance.goBack<bool>(true);
4. 测试用例分析 #
让我们分析提供的测试用例,了解每个页面的用途和场景。
登录页面 (/login
) #
登录页面用于用户身份验证,它接受一个可选的 returnTo
参数,指定登录成功后要返回的页面:
router.register('/login', (config) => LoginPage(returnTo: config.params?['returnTo']));
登录页面的主要功能:
- 提供用户名和密码输入字段
- 验证用户输入
- 登录成功后导航到指定页面或默认页面
- 提供注册链接
测试场景:
- 直接访问登录页
- 从受保护页面重定向到登录页,登录成功后返回原页面
- 测试表单验证逻辑
- 测试登录失败的错误处理
仪表板页面 (/dashboard
) #
仪表板页面是一个受保护的页面,需要用户登录才能访问:
router.register('/dashboard', (config) => DashboardPage());
仪表板页面的主要功能:
- 显示用户数据概览
- 提供导航到其他功能页面的入口
- 包含抽屉菜单和操作按钮
测试场景:
- 已登录用户访问仪表板
- 未登录用户尝试访问仪表板(应被重定向到登录页)
- 测试从仪表板导航到其他页面
- 测试抽屉菜单的功能
个人资料页面 (/profile
) #
个人资料页面是一个既需要身份验证又需要特定权限的页面:
router.register('/profile', (config) => ProfilePage());
个人资料页面的主要功能:
- 显示用户个人信息
- 允许编辑部分信息
- 提供安全设置和密码修改入口
- 包含退出登录功能
测试场景:
- 已登录且有权限的用户访问个人资料页
- 已登录但无权限的用户尝试访问(应被重定向到未授权页)
- 未登录用户尝试访问(应被重定向到登录页)
- 测试编辑功能和保存更改
未授权页面 (/unauthorized
) #
未授权页面用于处理用户没有权限访问某些页面的情况:
router.register('/unauthorized', (config) => UnauthorizedPage());
未授权页面的主要功能:
- 显示访问被拒绝的信息
- 提供返回首页的选项
- 提供联系管理员的选项
- 提供切换账号的选项
测试场景:
- 用户尝试访问没有权限的页面时被重定向到此页面
- 测试各个导航按钮的功能
编写测试用例 #
以下是一个简单的测试用例,用于验证身份验证拦截器的功能:
void main() {
testWidgets('Auth interceptor redirects to login page', (WidgetTester tester) async {
// 设置测试环境
final router = ContextFreeRouter.instance as ContextFreeRouterImpl;
bool isAuthenticated = false;
router.register('/', (config) => HomePage());
router.register('/login', (config) => LoginPage(returnTo: config.params?['returnTo']));
router.register('/dashboard', (config) => DashboardPage());
router.addInterceptor(AuthInterceptor(
protectedRoutes: ['/dashboard'],
loginRoute: '/login',
isAuthenticatedCallback: () async => isAuthenticated,
));
// 渲染应用
await tester.pumpWidget(MaterialApp(
navigatorKey: router.navigatorKey,
home: HomePage(),
));
// 尝试导航到受保护页面
ContextFreeRouter.instance.navigateTo('/dashboard');
await tester.pumpAndSettle();
// 验证是否被重定向到登录页
expect(find.byType(LoginPage), findsOneWidget);
expect(find.byType(DashboardPage), findsNothing);
// 模拟登录成功
isAuthenticated = true;
// 再次尝试导航到受保护页面
ContextFreeRouter.instance.navigateTo('/dashboard');
await tester.pumpAndSettle();
// 验证是否成功导航到仪表板
expect(find.byType(DashboardPage), findsOneWidget);
expect(find.byType(LoginPage), findsNothing);
});
}
5. 监控功能介绍 #
ContextFreeRouter 提供了强大的路由监控功能,可以跟踪路由变化和相关数据。这对于分析用户行为、调试问题和集成分析工具非常有用。
监控接口 #
所有监控器都需要实现 RouteMonitor
接口:
abstract class RouteMonitor {
/// 当路由成功导航时调用
void onRouteChanged(RouteConfig from, RouteConfig to);
/// 当路由导航被取消时调用
void onRouteCancelled(RouteConfig from, RouteConfig to);
/// 当路由导航被重定向时调用
void onRouteRedirected(RouteConfig from, RouteConfig to, RouteConfig redirectTo);
/// 当导航过程中发生错误时调用
void onRouteError(RouteConfig from, RouteConfig to, Object error);
}
分析监控器示例 #
以下是一个用于分析的监控器实现:
class AnalyticsMonitor implements RouteMonitor {
final void Function(String event, Map<String, dynamic> properties) trackEvent;
AnalyticsMonitor({
required this.trackEvent,
});
@override
void onRouteChanged(RouteConfig from, RouteConfig to) {
trackEvent('route_changed', {
'from': from.path,
'to': to.path,
'params': to.params,
});
}
@override
void onRouteCancelled(RouteConfig from, RouteConfig to) {
trackEvent('route_cancelled', {
'from': from.path,
'to': to.path,
});
}
@override
void onRouteRedirected(RouteConfig from, RouteConfig to, RouteConfig redirectTo) {
trackEvent('route_redirected', {
'from': from.path,
'to': to.path,
'redirectTo': redirectTo.path,
});
}
@override
void onRouteError(RouteConfig from, RouteConfig to, Object error) {
trackEvent('route_error', {
'from': from.path,
'to': to.path,
'error': error.toString(),
});
}
}
日志监控器示例 #
以下是一个简单的日志监控器,用于记录路由变化:
class LoggingMonitor implements RouteMonitor {
final Logger logger;
LoggingMonitor({
required this.logger,
});
@override
void onRouteChanged(RouteConfig from, RouteConfig to) {
logger.info('Route changed from ${from.path} to ${to.path}');
}
@override
void onRouteCancelled(RouteConfig from, RouteConfig to) {
logger.warning('Route navigation cancelled from ${from.path} to ${to.path}');
}
@override
void onRouteRedirected(RouteConfig from, RouteConfig to, RouteConfig redirectTo) {
logger.info('Route redirected from ${from.path} to ${redirectTo.path} (original: ${to.path})');
}
@override
void onRouteError(RouteConfig from, RouteConfig to, Object error) {
logger.error('Route error from ${from.path} to ${to.path}: ${error.toString()}');
}
}
添加监控器 #
你可以在应用初始化时添加监控器:
final router = ContextFreeRouter.instance as ContextFreeRouterImpl;
// 添加分析监控器
router.addMonitor(AnalyticsMonitor(
trackEvent: (event, properties) {
// 集成你的分析服务,如 Firebase Analytics
FirebaseAnalytics.instance.logEvent(
name: event,
parameters: properties,
);
},
));
// 添加日志监控器
router.addMonitor(LoggingMonitor(
logger: Logger(),
));
6. 总结与展望 #
总结 #
ContextFreeRouter 插件为 Flutter 应用提供了一种强大而灵活的路由解决方案,它的主要优点包括:
- 无 BuildContext 依赖:可以在应用的任何位置进行导航,无需传递上下文
- 强大的拦截器机制:支持身份验证、权限检查等自定义操作
- 全面的监控能力:可以跟踪路由变化和相关数据
- 简单的 API:提供直观的路由注册和导航方法
- 参数传递:支持通过
params
和extra
传递数据
这些特性使 ContextFreeRouter 特别适合以下场景:
- 大型应用,需要在多个层级进行导航
- 需要复杂权限控制的应用
- 需要详细分析用户导航行为的应用
- 采用清晰架构或 MVVM 模式的应用,需要在视图模型中进行导航
未来展望 #
ContextFreeRouter 插件还有很多可以改进和扩展的方向:
- 路由动画:添加自定义过渡动画支持,使页面切换更加流畅和美观
- 路径参数:实现类似
/users/:id
的路径参数支持,使路由更加灵活 - 嵌套导航:支持嵌套导航器,适用于底部标签栏和抽屉菜单等复杂导航结构
- 深层链接:增强对深层链接和外部 URL 的支持
- 路由缓存:实现路由页面缓存机制,提高性能和用户体验
- 声明式路由配置:提供更声明式的路由配置方式,使路由定义更加清晰
- 类型安全:增强类型安全,减少运行时错误
结语 #
ContextFreeRouter 插件通过解决 Flutter 导航中的 BuildContext 依赖问题,为开发者提供了更大的灵活性和更清晰的代码结构。它的拦截器机制和监控能力使复杂的导航逻辑变得简单而强大。
无论你是在开发一个简单的应用还是一个复杂的企业级项目,ContextFreeRouter 都能帮助你更好地管理应用的导航流程,提高开发效率和代码质量。
希望这篇文章能帮助你理解和使用 ContextFreeRouter 插件,如果你有任何问题或建议,欢迎在评论区留言或提交 issue 到我们的 GitHub 仓库。
参考资源