我的解决方案是构建一个路由保护系统,就像其他库一样,但我们仍然可以在需要的地方使用原始导航器,打开一个模式作为命名路由,链保护,并添加重定向。它非常基础,但可以很容易地构建。
看起来很多,但你只需要 3 个新文件来维护你的新守卫:
- router/guarded_material_page_route.dart
- router/route_guard.dart
- router/safe_navigator.dart
// Your guards go in here
- guards/auth_guard.dart
...
首先创建一个扩展MaterialPageRoute 的新类,或者如果您像我一样想要打开Modal Bottom Sheet package 或MaterialWithModalsPageRoute。我已经调用了我的 GuardedMaterialPageRoute
class GuardedMaterialPageRoute extends MaterialWithModalsPageRoute {
final List<RouteGuard> routeGuards;
GuardedMaterialPageRoute({
// ScrollController is only needed if you're using the modals, as i am in this example.
@required Widget Function(BuildContext, [ScrollController]) builder,
RouteSettings settings,
this.routeGuards = const [],
}) : super(
builder: builder,
settings: settings,
);
}
您的路线守卫将如下所示:
class RouteGuard {
final Future<bool> Function(BuildContext, Object) guard;
RouteGuard(this.guard);
Future<bool> canActivate(BuildContext context, Object arguments) async {
return guard(context, arguments);
}
}
您现在可以像这样将GuardedMaterialPageRoutes 添加到您的路由器文件中:
class Routes {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case homeRoute:
// These will still work with our new Navigator!
return MaterialPageRoute(
builder: (context) => HomeScreen(),
settings: RouteSettings(name: homeRoute),
);
case locationRoute:
// Following the same syntax, just with a routeGuards array now.
return GuardedMaterialPageRoute(
// Again, scrollController is only if you're opening a modal as a named route.
builder: (context, [scrollController]) {
final propertiesBloc = BlocProvider.of<PropertiesBloc>(context);
final String locationId = settings.arguments;
return BlocProvider(
create: (_) => LocationBloc(
locationId: locationId,
propertiesBloc: propertiesBloc,
),
child: LocationScreen(),
);
},
settings: RouteSettings(name: locationRoute),
routeGuards: [
// Now inject your guards, see below for what they look like.
AuthGuard(),
]
);
...
像上面那样创建你的异步保护类,就像我们在路由器中使用的那样。
class AuthGuard extends RouteGuard {
AuthGuard() : super((context, arguments) async {
final auth = Provider.of<AuthService>(context, listen: false);
const isAnonymous = await auth.isAnonymous();
return !isAnonymous;
});
}
现在您需要一个新的类来处理您的导航。在这里你检查你是否有访问权限并简单地穿过每个守卫:
class SafeNavigator extends InheritedWidget {
static final navigatorKey = GlobalKey<NavigatorState>();
@override
bool updateShouldNotify(SafeNavigator oldWidget) {
return false;
}
static Future<bool> popAndPushNamed(
String routeName, {
Object arguments,
bool asModalBottomSheet = false,
}) async {
Navigator.of(navigatorKey.currentContext).pop();
return pushNamed(routeName, arguments: arguments, asModalBottomSheet: asModalBottomSheet);
}
static Future<bool> pushNamed(String routeName, {
Object arguments,
bool asModalBottomSheet = false,
}) async {
// Fetch the Route Page object
final settings = RouteSettings(name: routeName, arguments: arguments);
final route = Routes.generateRoute(settings);
// Check if we can activate it
final canActivate = await _canActivateRoute(route);
if (canActivate) {
// Only needed if you're using named routes as modals, under the hood the plugin still uses the Navigator and can be popped etc.
if (asModalBottomSheet) {
showCupertinoModalBottomSheet(
context: navigatorKey.currentContext,
builder: (context, scrollController) =>
(route as GuardedMaterialPageRoute)
.builder(context, scrollController));
} else {
Navigator.of(navigatorKey.currentContext).push(route);
}
}
return canActivate;
}
static Future<bool> _canActivateRoute(MaterialPageRoute route) async {
// Check if it is a Guarded route
if (route is GuardedMaterialPageRoute) {
// Check all guards on the route
for (int i = 0; i < route.routeGuards.length; i++) {
// Run the guard
final canActivate = await route.routeGuards[i]
.canActivate(navigatorKey.currentContext, route.settings.arguments);
if (!canActivate) {
return false;
}
}
}
return true;
}
}
要使这一切正常工作,您需要将 SafeNavigator 密钥添加到您的 Material 应用程序:
MaterialApp(
navigatorKey: SafeNavigator.navigatorKey,
...
)
现在您可以导航到您的路线并检查您是否可以像这样访问它们:
// Opens a named route, either Guarded or not.
SafeNavigator.pushNamed(shortlistRoute);
// Opens a named route as a modal
SafeNavigator.pushNamed(shortlistRoute, asModalBottomSheet: true);
// Pops the current route and opens a named route as a modal
SafeNavigator.popAndPushNamed(shortlistRoute, asModalBottomSheet: true);