之前咱们介绍过Flutter 的一个路由办理器 fluro,本篇咱们介绍另一个十分受欢迎的路由办理第三方插件:go_router。本文翻译自 medium,为了便于阅览,做了些许个人理解后的修正,原文地址:medium.com/@antonio.ti…。
前语
go_router
是一个 Flutter 的第三方路由插件,相比 Flutter 自带的路由,go_router
愈加灵敏,而且简略易用。在 App 运用中,假如你想自己操控路由的界说和办理方法,那么它将十分有用。一起,关于 Web 运用来说,go_router
也供给了很好的支撑。
运用 go_router
后,你能够界说 URL 的格式,运用 URL 跳转,处理深度链接以及其他一系列的导航相关的运用场景。
GoRouter 特性
GoRouter
针对页面导航供给了下面这些特性:
- 运用模板语法解析路由途径和路由查询(
query
)参数; - 支撑单个目标路由展现多个页面(子路由);
- 重定向:能够基于运用状况跳转到不同的URL,比方用户没有登录时跳转到登录页;
- 运用
StatefulShellRoute
能够支撑嵌套的 Tab 导航; - 一起支撑
Material
风格和Cupertino
风格运用; - 兼容
Navigator
API 。
添加插件
当时最新版别的 go_router
是10.0.0(6.3.0版别以上需求 Dart 2.18),能够依据自己的需求添加相应的版别。在 pubspec.yaml 中参加依赖的版别即可,下面是以7.1.1版别为例。
dependencies:
go_router: ^7.1.1
路由装备
引进 go_router
插件后,就能够在运用中装备 GoRouter
,代码如下:
import 'package:go_router/go_router.dart';
// GoRouter configuration
final _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
name: 'home', // Optional, add name to your routes. Allows you navigate by name instead of path
path: '/',
builder: (context, state) => HomeScreen(),
),
GoRoute(
name: 'page2',
path: '/page2',
builder: (context, state) => Page2Screen(),
),
],
);
然后,咱们就能够经过MaterialApp.router
或CupertinoApp.router
结构函数来运用 GoRouter
,而且将 routerConfig
参数设置为咱们前面界说的 GoRouter
装备目标。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
接下来就能够愉快地玩耍 GoRouter
了。
路由参数
GoRouter
的每一个路由都经过 GoRoute
目标来装备,咱们能够在构建 GoRoute
目标时来装备路由参数。路由参数典型的便是途径参数,比方 /path/:{途径参数}
,这个时分 GoRoute
的途径参数和很多 Web 框架的路由是相同的,经过一个英文冒号加参数称号就能够装备,之后咱们能够在回调办法中经过 GoRouterState
目标获取途径参数,这个参数就能够传递到路由跳转目的页面。
GoRoute(
path: '/fruits/:id',
builder: (context, state) {
final id = state.params['id'] // Get "id" param from URL
return FruitsPage(id: id);
},
),
同样,也能够从GoRouterState
中获取 URL 途径中的查询(query)参数,例如下面的代码便是从/fruits?search=antonio
中获取search
参数。
GoRoute(
path: '/fruits',
builder: (context, state) {
final search = state.queryParams['search'];
return FruitsPage(search: search);
},
),
添加子路由
路由匹配后能够支撑多个页面(即子路由),当一个新的页面在旧的页面之上展现时,这个时分的效果和调用 push
办法是相同的,。假如页面供给了 AppBar 组件的话,那么会自动添加回来按钮。
要运用子路由,咱们只需求在上级路由中添加对应的下级路由即可,代码如下。
GoRoute(
path: '/fruits',
builder: (context, state) {
return FruitsPage();
},
routes: <RouteBase>[ // Add child routes
GoRoute(
path: 'fruits-details', // NOTE: Don't need to specify "/" character for router’s parents
builder: (context, state) {
return FruitDetailsPage();
},
),
],
)
页面导航
GoRouter 供给了多种方法跳转到目的页面,比方运用context.go()
跳转到指定的 URL 地址。
build(BuildContext context) {
return TextButton(
onPressed: () => context.go('/fruits/fruit-detail'),
);
}
也能够运用路由的称号进行跳转,这个时分调用context.goNamed()
即可。
build(BuildContext context) {
return TextButton(
// remember to add "name" to your routes
onPressed: () => context.goNamed('fruit-detail'),
);
}
假如要构建查询参数,那么能够运用 Uri 类来构建路由途径。
context.go(
Uri(
path: '/fruit-detail',
queryParameters: {'id': '10'},
).toString(),
);
假如要从当时页面回来的话,调用context.pop()
即可。
嵌套导航
有些运用在同一个页面展现多个子页面,例如 BottomNavigationBar
在进行导航的时分就能够一向保留在屏幕底部。这种其实是嵌套导航,在 GoRouter
里,能够经过StatefulShellRoute
来完结。
StatefulShellRoute
不直接运用根导航(root Navigator),而是经过不同的导航的子路由来完结嵌套导航。关于每一个导航分支,都会创立各自独立的导航,适当所以一个并行导航树,然后完结了有状况的嵌套导航。
当咱们运用BottomNavigationBar
的时分,就能够为很方便地为每一个 Tab 装备一个耐久的导航状况。
StatefulShellRoute
经过指定一个StatefulShellBranch
类型的列表来完结,列表每一个元素代表路由树的一个独立的有状况的分支。StatefulShellBranch
为每个分支供给了根路由和 Navigator
key(GlobalKey
),并供给了可选的初始默许路由地址。
咱们来看看具体怎样完结。
首要创立咱们的 GoRouter
目标,这个时分咱们需求添加StatefulShellRoute.indexedStack
到路由中,这个类负责创立嵌套路由。StatefulShellRoute.indexedStack() 实际上是运用了 IndexedStack
创立了一个StatefulShellRoute
。
这个结构函数运用IndexedStack
来办理每个分支导航的页面,示例代码如下:
// Create keys for `root` & `section` navigator avoiding unnecessary rebuilds
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _sectionNavigatorKey = GlobalKey<NavigatorState>();
final router = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/feed',
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
// Return the widget that implements the custom shell (e.g a BottomNavigationBar).
// The [StatefulNavigationShell] is passed to be able to navigate to other branches in a stateful way.
return ScaffoldWithNavbar(navigationShell);
},
branches: [
// The route branch for the 1 Tab
StatefulShellBranch(
navigatorKey: _sectionNavigatorKey,
// Add this branch routes
// each routes with its sub routes if available e.g feed/uuid/details
routes: <RouteBase>[
GoRoute(
path: '/feed',
builder: (context, state) => const FeedPage(),
routes: <RouteBase>[
GoRoute(
path: 'detail',
builder: (context, state) => const FeedDetailsPage(),
)
],
),
],
),
// The route branch for 2 Tab
StatefulShellBranch(routes: <RouteBase>[
// Add this branch routes
// each routes with its sub routes if available e.g shope/uuid/details
GoRoute(
path: '/shope',
builder: (context, state) => const ShopePage(),
),
])
],
),
],
);
在上面的代码中,咱们在路由中参加了StatefulShellRoute.indexedStack()
,由它负责创立路由分支以及回来一个自界说的导航壳,这儿是BottomNavigationBar
。
- 在 builder 参数中, 咱们回来导航用的壳,一个简略的带有
BottomNavigationBar
的Scaffold
,这儿需求记住将navigationShell
传给页面,因为咱们需求用它来导航到其他分支,例如从Home到 Shope。 - 在路由分支数组
branches
中,咱们供给了一个StatefulShellBranch
的数组。这儿只需求给第一个元素的navigatorKey
供给之前创立的大局的_sectionNavigatorKey
。其他分支则运用默许的 key。一起,为每个分支供给了一个RouteBase
列表,该列表是对应分支的路由。
下面是咱们界说的带有BottomNavigationBar
的自界说导航壳的代码。
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class ScaffoldWithNavbar extends StatelessWidget {
const ScaffoldWithNavbar(this.navigationShell, {super.key});
/// The navigation shell and container for the branch Navigators.
final StatefulNavigationShell navigationShell;
@override
Widget build(BuildContext context) {
return Scaffold(
body: navigationShell,
bottomNavigationBar: BottomNavigationBar(
currentIndex: navigationShell.currentIndex,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.shop), label: 'Shope'),
],
onTap: _onTap,
),
);
}
void _onTap(index) {
navigationShell.goBranch(
index,
// A common pattern when using bottom navigation bars is to support
// navigating to the initial location when tapping the item that is
// already active. This example demonstrates how to support this behavior,
// using the initialLocation parameter of goBranch.
initialLocation: index == navigationShell.currentIndex,
);
}
}
在上面的代码中,实际上便是构建带有BottomNavigationBar
的Scaffold
然后 body 是从路由里获取的navigationShell
.
路由分支及页面的切换经过_onTap(index)
完结,当点击某个 Tab 时,就运用navigationShell.goBranch(index)
来完结切换动作。完好代码:flutter-go_router-with-nested-tab-navigation。
路由护卫(Guards)
有些路由地址需求护卫,例如关于没有登录的用户,有些页面就无法访问。GoRouter
能够设置大局的重定向路由。最常见的一个场景便是关于没有登录的用户,跳转到/login
登录页面。
在 GoRouter 中,能够经过redirect
参数装备重定向,这是一个GoRouterRedirect
的回调办法。假如要基于运用状况更改跳转都只,那么既能够在GoRouter
或GoRoute
的结构办法中添加redirect
参数。其中 GoRoute
是只针对当时路由进行跳转处理,而GoRouter
这是大局处理。下面是示例代码,假如不需求跳转,则在回调办法中回来 null
即可。
GoRouter(
redirect: (BuildContext context, GoRouterState state) {
final isAuthenticated = // your logic to check if user is authenticated
if (!isAuthenticated) {
return '/login';
} else {
return null; // return "null" to display the intended route without redirecting
}
},
...
也能够指定一个redirectLimit
参数来限制最大的跳转次数,这个值默许是5。假如超过了跳转次数,则会显示一个过错页面。
转场动画
GoRouter
支撑为每个 GoRoute
自界说转场动画,这能够经过GoRoute
的结构函数的pageBuilder
参数来完结,下面是示例代码。
GoRoute(
path: '/fruit-details',
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: FruitDetailsScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// Change the opacity of the screen using a Curve based on the the animation's value
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
},
);
},
),
完好的示例代码能够在 GitHub 检查:自界说转场动画源码。
过错处理(404页面)
go_router
为MaterialApp
和CupertinoApp
界说了默许的过错页面,也能够经过 errorBuilder 参数自界说过错页面,代码如下。
GoRouter(
/* ... */
errorBuilder: (context, state) => ErrorPage(state.error),
);
类型安全路由
除了运用 URL 进行路由导航外,go_router 也经过go_router_builder
插件供给了类型安全路由,这能够经过代码生成来完结。要运用这种方法,需求在pubspec.yaml
添加下面这些依赖。
dev_dependencies:
go_router_builder: ^1.0.16
build_runner: ^2.3.3
build_verify: ^3.1.0
界说路由
Then define each route as a class extending GoRouteData and overriding the build method.
class HomeRoute extends GoRouteData {
const HomeRoute();
@override
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}
路由树
路由树基于每个顶层的路由来界说,代码如下。
import 'package:go_router/go_router.dart';
part 'go_router.g.dart'; // name of generated file
// Define how your route tree (path and sub-routes)
@TypedGoRoute<HomeScreenRoute>(
path: '/home',
routes: [ // Add sub-routes
TypedGoRoute<SongRoute>(
path: 'song/:id',
)
]
)
// Create your route screen that extends "GoRouteData" and @override "build"
// method that return the screen for this route
@immutable
class HomeScreenRoute extends GoRouteData {
@override
Widget build(BuildContext context) {
return const HomeScreen();
}
}
@immutable
class SongRoute extends GoRouteData {
final int id;
const SongRoute({required this.id});
@override
Widget build(BuildContext context) {
return SongScreen(songId: id.toString());
}
}
之后能够运行代码生成来构建类型安全路由。
flutter pub global activate build_runner // Optional, if you already have build_runner activated so you can skip this step
flutter pub run build_runner build
导航的时分,就不再需求运用 URL的方法了,能够构建一个GoRouteData目标然后调用go()
即可。
TextButton(
onPressed: () {
const SongRoute(id: 2).go(context);
},
child: const Text('Go to song 2'),
),
路由跳转监测
go_router还供给了一个十分有用的特性,那便是路由导航监测NavigatorObserver
。能够经过给GoRouter添加一个NavigatorObserver
目标来监听路由行为,例如 push
、pop
或路由替换(replace
)。这能够经过自界说 NavigatorObserver
的子类完结。
class MyNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
log('did push route');
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
log('did pop route');
}
}
之后,在GoRouter
的 observers
参数中添加自界说的MyNavigatorObserver
即可完结对所有触发路由跳转的行为的监听。
GoRouter(
...
observers: [ // Add your navigator observers
MyNavigatorObserver(),
],
...
)
完好的示例代码见 GitHub:
flutter-with-go_router: A simple app to show how to work with go_router。
我是岛上码农,微信大众号同名。如有问题能够加自己微信沟通,微信号:
island-coder
。:觉得有收获请点个赞鼓舞一下!
:收藏文章,方便回看哦!
:谈论沟通,互相进步!