Fluro是Flutter的一个很方便的路由框架。最近做一个demo,使用Fluro做路由跳转,有页面Page1,Page2,LoginPage,Page1->Page2需要先登录,也就是说没登录的时候我们需要将路由重定向到LoginPage,就这么简单的一个需求!
简单实现,在跳转的时候判断是否登录,登录了就跳转到Page2,否则就是跳转到LoginPage,如下:
static Future pageTo(context, String route,
{bool needLogin = false,
bool replace = false,
bool clearStack = false,
TransitionType transition = TransitionType.cupertino,
Map<String, dynamic> param}) {
if (needLogin && !Provider.of<UserModel>(context, listen: false).hasUser) {
route = Routers.login;
transition = TransitionType.cupertinoFullScreenDialog;
}
if (param != null && param.isNotEmpty) {
route = route + "?json=${Utils.convertMapToJson(param)}";
}
return Application.router.navigateTo(context, route,
transition: transition,
replace: replace,
clearStack: clearStack,
transitionDuration: Duration(milliseconds: 200));
}
可以看出,在跳转的时候传入needLogin=true的时候就会判断是否已经登录,这里的
Provider.of<UserModel>(context, listen: false).hasUser
就是判断用户是否登录,没有登录就改变route路由以及它的跳转样式。
这段代码虽然逻辑上没问题,但是看着不太舒服,所以我就在想,能否做到我们跳转的时候可以无需考虑用户是否已经登录这些个条件,无感跳转呢?那我就想一个Fluro路由是否有拦截器功能,我们可以提前设置好需要拦截的路由,并且指定拦截后重定向的路由。Fluro框架的代码,
看了Fluro的代码并没有发现拦截器功能,但是发现有个自定义NotFoundPage 的功能,也就是指定找不到路由的时候跳转的页面:
class Router {
static final appRouter = Router();
/// The tree structure that stores the defined routes
final RouteTree _routeTree = RouteTree();
/// Generic handler for when a route has not been defined
Handler notFoundHandler;
/// Creates a [PageRoute] definition for the passed [RouteHandler]. You can optionally provide a default transition type.
void define(String routePath,
{@required Handler handler, TransitionType transitionType}) {
_routeTree.addRoute(
AppRoute(routePath, handler, transitionType: transitionType),
);
}
///
Future navigateTo(BuildContext context, String path,
{bool replace = false,
bool clearStack = false,
TransitionType transition,
Duration transitionDuration = const Duration(milliseconds: 250),
RouteTransitionsBuilder transitionBuilder}) {
RouteMatch routeMatch = matchRoute(context, path,
transitionType: transition,
transitionsBuilder: transitionBuilder,
transitionDuration: transitionDuration);
Route<dynamic> route = routeMatch.route;
Completer completer = Completer();
Future future = completer.future;
if (routeMatch.matchType == RouteMatchType.nonVisual) {
completer.complete("Non visual route type.");
} else {
///重定向到NotFound路由
if (route == null && notFoundHandler != null) {
route = _notFoundRoute(context, path);
}
if (route != null) {
if (clearStack) {
future =
Navigator.pushAndRemoveUntil(context, route, (check) => false);
} else {
future = replace
? Navigator.pushReplacement(context, route)
: Navigator.push(context, route);
}
completer.complete();
} else {
String error = "No registered route was found to handle '$path'.";
print(error);
completer.completeError(RouteNotFoundException(error, path));
}
}
return future;
}
///这里自定义NotFound路由的一些特性
Route<Null> _notFoundRoute(BuildContext context, String path) {
RouteCreator<Null> creator =
(RouteSettings routeSettings, Map<String, List<String>> parameters) {
if (Platform.isIOS) {
return CupertinoPageRoute<Null>(
settings: routeSettings,
fullscreenDialog: true,
builder: (BuildContext context) {
return notFoundHandler
.handlerFunc(context, parameters, extra: {"path": path});
});
} else {
return MaterialPageRoute<Null>(
settings: routeSettings,
fullscreenDialog: true,
builder: (BuildContext context) {
return notFoundHandler
.handlerFunc(context, parameters, extra: {"path": path});
});
}
};
return creator(RouteSettings(name: path), null);
}
///路由匹配函数
RouteMatch matchRoute(BuildContext buildContext, String path,
{RouteSettings routeSettings,
TransitionType transitionType,
Duration transitionDuration = const Duration(milliseconds: 250),
RouteTransitionsBuilder transitionsBuilder}) {
RouteSettings settingsToUse = routeSettings;
if (routeSettings == null) {
settingsToUse = RouteSettings(name: path);
}
AppRouteMatch match = _routeTree.matchRoute(path);
AppRoute route = match?.route;
///如果没有匹配上路由,就跳转到自定义的NotFound路由
Handler handler = (route != null ? route.handler : notFoundHandler);
var transition = transitionType;
if (transitionType == null) {
transition = route != null ? route.transitionType : TransitionType.native;
}
if (route == null && notFoundHandler == null) {
return RouteMatch(
matchType: RouteMatchType.noMatch,
errorMessage: "No matching route was found");
}
Map<String, List<String>> parameters =
match?.parameters ?? <String, List<String>>{};
if (handler.type == HandlerType.function) {
handler.handlerFunc(buildContext, parameters);
return RouteMatch(matchType: RouteMatchType.nonVisual);
}
RouteCreator creator =
(RouteSettings routeSettings, Map<String, List<String>> parameters) {
bool isNativeTransition = (transition == TransitionType.native ||
transition == TransitionType.nativeModal);
if (isNativeTransition) {
if (Platform.isIOS) {
return CupertinoPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog: transition == TransitionType.nativeModal,
builder: (BuildContext context) {
Widget targetWidget = handler.handlerFunc(context, parameters);
return targetWidget;
});
} else {
return MaterialPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog: transition == TransitionType.nativeModal,
builder: (BuildContext context) {
return handler.handlerFunc(context, parameters);
});
}
} else if (transition == TransitionType.material ||
transition == TransitionType.materialFullScreenDialog) {
return MaterialPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog:
transition == TransitionType.materialFullScreenDialog,
builder: (BuildContext context) {
return handler.handlerFunc(context, parameters);
});
} else if (transition == TransitionType.cupertino ||
transition == TransitionType.cupertinoFullScreenDialog) {
return CupertinoPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog:
transition == TransitionType.cupertinoFullScreenDialog,
builder: (BuildContext context) {
return handler.handlerFunc(context, parameters);
});
} else {
var routeTransitionsBuilder;
if (transition == TransitionType.custom) {
routeTransitionsBuilder = transitionsBuilder;
} else {
routeTransitionsBuilder = _standardTransitionsBuilder(transition);
}
return PageRouteBuilder<dynamic>(
settings: routeSettings,
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return handler.handlerFunc(context, parameters);
},
transitionDuration: transitionDuration,
transitionsBuilder: routeTransitionsBuilder,
);
}
};
return RouteMatch(
matchType: RouteMatchType.visual,
route: creator(settingsToUse, parameters),
);
}
从我添加的三个注释的地方代码可以看出Fluro是怎么跳转到自定义的NotFound页面的,既然这样那我也照此来写一个自定义的拦截路由,在上边代码中增加以下内容:
class Router {
///定义拦截器
typedef bool Interceptor();
///重定向的页面路由处理器
Handler redirectHandler;
///拦截器
Interceptor interceptor;
///路由匹配函数
RouteMatch matchRoute(BuildContext buildContext, String path,
{RouteSettings routeSettings,
TransitionType transitionType,
Duration transitionDuration = const Duration(milliseconds: 250),
RouteTransitionsBuilder transitionsBuilder}) {
RouteSettings settingsToUse = routeSettings;
if (routeSettings == null) {
settingsToUse = RouteSettings(name: path);
}
AppRouteMatch match = _routeTree.matchRoute(path);
AppRoute route = match?.route;
Handler handler = (route != null ? route.handler : notFoundHandler);
var transition = transitionType;
if (transitionType == null) {
transition = route != null ? route.transitionType : TransitionType.native;
}
if (route == null && notFoundHandler == null) {
return RouteMatch(
matchType: RouteMatchType.noMatch,
errorMessage: "No matching route was found");
}
Map<String, List<String>> parameters =
match?.parameters ?? <String, List<String>>{};
///
///这里Fluro匹配到对应的路由处理器后取出intercept参数,
//判断是否需要拦截,如果需要拦截再判断拦截器的条件是否满足,
//如果不满足条件,比如没有登录,那么我们就重定向到我们自定的路由页面,如登录页面
if (route?.handler?.intercept == true &&
interceptor != null &&
!interceptor()) {
return RouteMatch(
matchType: RouteMatchType.intercept,
route: _redirectRoute(buildContext, path),
errorMessage: "your route has been intercepted");
}
/
}
///重定向的路由处理器,返回指定的重定向的路由页面,这里加了个extra的参数,是为了判断发起跳转的页面是谁。
Route<dynamic> _redirectRoute(BuildContext context, String path) {
RouteCreator<dynamic> creator =
(RouteSettings routeSettings, Map<String, List<String>> parameters) {
if (Platform.isIOS) {
return CupertinoPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog: true,
builder: (BuildContext context) {
return redirectHandler
.handlerFunc(context, parameters, extra: {"path": path});
});
} else {
return MaterialPageRoute<dynamic>(
settings: routeSettings,
fullscreenDialog: true,
builder: (BuildContext context) {
return redirectHandler
.handlerFunc(context, parameters, extra: {"path": path});
});
}
};
return creator(RouteSettings(name: path), null);
}
.....
}
class Handler {
Handler(
{this.type = HandlerType.route,
this.handlerFunc,
this.intercept = false});
final HandlerType type;
final HandlerFunc handlerFunc;
final bool intercept;//路由处理器中增加一个是否是否拦截的参数
}
enum RouteMatchType {
visual,
nonVisual,
noMatch,
intercept,//新增一个intercept类型
}
接下来我们只需要设置重定向的页面、拦截器、需要拦截的路由就可以了
定义路由映射
class Routers {
static const root = '/';
static const page1= '/page1';
static const page2= '/page2';
static const login = '/person/login';
static configureRouters(Router router) {
///缺省页
router.notFoundHandler = notFoundHandler;
///定义重定向的路由处理器,对应重定向的路由页面
router.redirectHandler=loginHandler;
///Page1
router.define(home, handler: homeHandler);
///Page2
router.define(collection, handler: collectionHandler);
///LoginPage
router.define(login, handler: loginHandler);
}
}
配置路由处理器,这里重写路由处理器方便参数的转化,Fluro的传递的参数是经过编码的
///自定义路由事件处理参数,将json转为map
class LdcHandler extends Handler {
final Logger logger = Logger("LdcHandler");
final LdcHandlerFunc ldcHandlerFunc;
final bool intercept;
LdcHandler({this.ldcHandlerFunc, this.intercept})
: super(intercept: intercept);
@override
HandlerFunc get handlerFunc =>
(context, params, {Map<String, dynamic> extra}) {
Map<String, dynamic> map;
if (params != null && params.length > 0 && params.containsKey('json')) {
map = Utils.convertJsonToMap(params['json'].first);
}
return ldcHandlerFunc(context, map, extra);
};
}
///定义一个自己的路由处理器
typedef Widget LdcHandlerFunc(BuildContext context,
Map<String, dynamic> parameters, Map<String, dynamic> extra);
///NotFound
Handler notFoundHandler =
LdcHandler(ldcHandlerFunc: (_, map, extra) => NotFoundWidget());
///Page1
Handler page1Handler =
LdcHandler(ldcHandlerFunc: (context, map, extra) => Page1());
///Page2,这里的intercept=true,这样Fluro就会拦截该路由
Handler page2Handler = LdcHandler(
intercept: true, ldcHandlerFunc: (context, map, extra) => Page2());
///登陆
Handler loginHandler = LdcHandler(
ldcHandlerFunc: (context, map, extra) => LoginPage(
scence: map != null
? map['scence']
: extra == null ? '' : (extra['path'] ?? '')));
配置拦截器
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends CustomState<MyHomePage> {
@override
Widget buildWidget(BuildContext context) {
......
///配置拦截器,判断用户是否登录
Application.router.interceptor =
() => Provider.of<UserModel>(context, listen: false).hasUser;
......
}
OK, 配置完成了,上边的NotFoundWidget、Page1、Page2、LoginPage就是简单的Widget,代码就不贴了,这样我们以后如果页面需要配置权限的时候,只需要在配置处理器的时候设置intercept=true,就可以了。
最后,还可以考虑配置多个Interceptor,实现方式就不说了。
本人小白一枚,本文仅提供一种实现方法,不喜勿喷~