packagist_route 1.0.0 packagist_route: ^1.0.0 copied to clipboard
Wrapper for go_router.
Route Manager #
Usage #
Define routes constants #
abstract class Routes {
static const main = Paths.main;
static const transition = Paths.transition;
static const transitionNone = "${Paths.transition}/${Paths.none}";
static const transitionFade = "${Paths.transition}/${Paths.fade}";
static const notFound = Paths.notFound;
}
abstract class Paths {
static const main = '/main';
static const transition = '/transition';
static const none = 'none';
static const fade = 'fade';
static const notFound = '/404';
}
Configure routing #
class AppPages {
AppPages._();
static final GoRouter router = GoRouter(
navigatorKey: Go.key,
observers: <NavigatorObserver>[
GoNavigatorObserver(Go.routing),
],
initialLocation: Paths.main,
routes: <RouteBase>[
goRoute(path: Paths.main, child: const MainPage()),
goRoute(path: Paths.transition, child: const TransitionPage(), routes: [
goRoute(
path: Paths.none,
child: const TransitionNextPage(),
transitionType: PageTransitionType.none,
),
goRoute(
path: Paths.fade,
child: const TransitionNextPage(),
transitionType: PageTransitionType.fade,
),
]),
],
redirect: (BuildContext context, GoRouterState state) {
logV("redirect: ${state.matchedLocation}");
// no need to redirect at all
return null;
},
onException: (context, GoRouterState state, GoRouter router) {
router.go(Routes.notFound, extra: {"uri": state.uri.toString()});
},
);
}
Go.key
should be initialized in GoRouter
as navigatorKey
property. Most of function
of GoRouter
is based on it.
GoNavigatorObserver(Go.routing),
should be initialized in GoRouter
as observers
property. It
is used to observe the route changes.
initialLocation
property is the first route to be displayed.
routes
is the list of routes to be used in the app.
redirect
is the function to decide if a route should be redirected to another route.
onException
is the function to be called when an exception is thrown.
How to navigate #
Go.go(Routes.transitionNone);
Go.push(Routes.transitionNone);
Go.to(Routes.transitionNone);
Go.go
is used to navigate to a route. It will push the current route to stack and change browser
url on web site.
Go.push
is used to navigate to a route. It will push the current route to stack but not change
browser url on web site.
Go.to
is wrapper of Go.push
and Go.go
, it will call Go.go
on web site and call Go.push
on
others.
Embedded pages on Web #
Synchronize browser Url when switching embedded pages.
TabBar and TabBarView #
Define a stateful shell route for tab bar.
static final GoRouter router = GoRouter(
initialLocation: Paths.main,
routes: <RouteBase>[
goRoute(path: Paths.main, child: const MainPage()),
// define a stateful shell route for tab bar
statefulShellRoute(
navigatorContainerBuilder: (context, navigationShell, children) =>
TabNavigatorWeb(navigationShell, children),
branches: <StatefulShellBranch>[
StatefulShellBranch(routes: <GoRoute>[
goRoute(path: Paths.tab1, child: const DetailPage("Tab1")),
]),
StatefulShellBranch(routes: <GoRoute>[
goRoute(path: Paths.tab2, child: const DetailPage("Tab2")),
]),
],
),
],
);
TabNavigatorWeb
: display tab bar and tab view. When switching tab bar (tab view), it will change
browser url. And when browser url is changed, it will switch tab bar (tab view). TabBar and TabView
will switch synchronously.
class TabNavigatorWeb extends StatefulWidget {
final StatefulNavigationShell navigationShell;
final List<Widget> children;
const TabNavigatorWeb(
this.navigationShell,
this.children, {
super.key,
});
@override
State<TabNavigatorWeb> createState() => _TabNavigatorWebState();
}
class _TabNavigatorWebState extends State<TabNavigatorWeb>
with SingleTickerProviderStateMixin {
late final TabController _tabController = TabController(
length: widget.children.length,
vsync: this,
initialIndex: widget.navigationShell.currentIndex);
int _currentIndex = 0;
@override
void initState() {
super.initState();
_tabController.addListener(() {
if (_currentIndex == _tabController.index) return;
widget.navigationShell.goBranch(_tabController.index);
});
}
@override
void didUpdateWidget(covariant TabNavigatorWeb oldWidget) {
super.didUpdateWidget(oldWidget);
_currentIndex = widget.navigationShell.currentIndex;
_tabController.index = _currentIndex;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TabNavigatorWeb'),
),
body: Column(children: [
TabBar(
controller: _tabController,
onTap: (int index) {
widget.navigationShell.goBranch(index);
},
tabs: const [
Tab(text: 'Tab1'),
Tab(text: 'Tab2'),
],
labelColor: Colors.black,
),
Expanded(
child: TabBarView(
controller: _tabController,
children: widget.children,
),
),
]),
);
}
}
BottomNavigationBar and PageView #
Define a stateful shell route for BottomNavigationBar.
static final GoRouter router = GoRouter(
initialLocation: Paths.main,
routes: <RouteBase>[
goRoute(path: Paths.main, child: const MainPage()),
// define a stateful shell route for BottomNavigationBar
statefulShellRoute(
navigatorContainerBuilder: (context, navigationShell, children) =>
PageNavigatorWeb(navigationShell, children),
branches: <StatefulShellBranch>[
StatefulShellBranch(routes: <GoRoute>[
goRoute(path: Paths.page1, child: const DetailPage("Page1")),
]),
StatefulShellBranch(routes: <GoRoute>[
goRoute(path: Paths.page2, child: const DetailPage("Page2")),
]),
],
),
],
);
PageNavigatorWeb
: display BottomNavigationBar and PageView. When switching BottomNavigationBar (
PageView), it will change browser url. And when browser url is changed, it will switch
BottomNavigationBar (PageView). PageView and BottomNavigationBar will switch synchronously.
class PageNavigatorWeb extends StatefulWidget {
final StatefulNavigationShell navigationShell;
final List<Widget> children;
const PageNavigatorWeb(
this.navigationShell,
this.children, {
super.key,
});
@override
State<PageNavigatorWeb> createState() => _PageNavigatorWebState();
}
class _PageNavigatorWebState extends State<PageNavigatorWeb> {
late PageController _pageController;
@override
void initState() {
super.initState();
initPageController();
}
@override
void didUpdateWidget(covariant PageNavigatorWeb oldWidget) {
super.didUpdateWidget(oldWidget);
initPageController();
}
void initPageController() {
_pageController = PageController(
initialPage: widget.navigationShell.currentIndex,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('NavigationBar'),
),
body: PageView(
controller: _pageController,
children: widget.children,
onPageChanged: (int index) {
widget.navigationShell.goBranch(index);
},
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
],
currentIndex: widget.navigationShell.currentIndex,
onTap: (int index) {
widget.navigationShell.goBranch(index);
_pageController.animateToPage(
index,
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
);
},
),
);
}
}
indexedStack #
Define a stateful shell route for indexedStack.
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) =>
StackNavigatorWeb(navigationShell),
branches: <StatefulShellBranch>[
StatefulShellBranch(routes: <GoRoute>[
goRoute(path: Paths.stack1, child: const DetailPage("Stack1")),
]),
StatefulShellBranch(routes: <GoRoute>[
goRoute(path: Paths.stack2, child: const DetailPage("Stack2")),
]),
],
),
StackNavigatorWeb
: Switching BottomNavigationBar to change the index of IndexedStack and browser
url.
class StackNavigatorWeb extends StatefulWidget {
final StatefulNavigationShell navigationShell;
const StackNavigatorWeb(
this.navigationShell, {
super.key,
});
@override
State<StackNavigatorWeb> createState() => _StackNavigatorWebState();
}
class _StackNavigatorWebState extends State<StackNavigatorWeb> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('IndexedStack'),
),
body: widget.navigationShell,
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
],
currentIndex: widget.navigationShell.currentIndex,
onTap: (int index) {
widget.navigationShell.goBranch(
index,
initialLocation: index == widget.navigationShell.currentIndex,
);
},
),
);
}
}
ShellRoute #
Define a ShellRoute for embedded navigation.
static final GoRouter router = GoRouter(
initialLocation: Paths.main,
routes: <RouteBase>[
goRoute(path: Paths.main, child: const MainPage()),
ShellRoute(
builder: (context, state, child) => ShellNavigatorWeb(child),
routes: <RouteBase>[
goRoute(
path: Paths.shell1,
child: const DetailPage("Shell1"),
transitionType: PageTransitionType.fade,
),
goRoute(
path: Paths.shell2,
child: const DetailPage("Shell2"),
transitionType: PageTransitionType.fade,
),
],
),
],
);
ShellNavigatorWeb
: route switch by browser url and BottomNavigationBar.
class ShellNavigatorWeb extends StatelessWidget {
final Widget child;
const ShellNavigatorWeb(this.child, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ShellRoute'),
),
body: child,
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'A Screen',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'B Screen',
),
],
currentIndex: _calculateSelectedIndex(context),
onTap: (int idx) => _onItemTapped(idx),
),
);
}
static int _calculateSelectedIndex(BuildContext context) {
final String location = GoRouterState.of(context).uri.toString();
if (location.startsWith(Routes.shell1)) {
return 0;
}
if (location.startsWith(Routes.shell1)) {
return 1;
}
return 0;
}
void _onItemTapped(int index) {
switch (index) {
case 0:
Go.go(Routes.shell1);
break;
case 1:
Go.go(Routes.shell2);
break;
}
}
}
Parameter passing #
Setting parameters #
static const argumentsPath = 'path/:title/:url';
Go.to(
argumentsPath,
params: {'title': '标题', 'url': '链接'},
queryParams: {'title': '标题', 'url': '链接'},
pathParams: {'title': '标题', 'url': '链接'},
);
On Web, queryParams
and pathParams
will change the browser url. params
will not change the
browser url, just pass the parameters to the next page.
queryParams
: the url will become path?title=标题&url=链接
.
pathParams
: the url will become path/标题/链接
.
On Other platforms, suggested to use params
.
Getting parameters #
model.title = Go.arguments['title'] ?? "null";
model.url = Go.arguments['url'] ?? "null";
NavigatorObserver #
Custom NavigatorObserver #
class DialogObserver extends NavigatorObserver {
/// Creates a [DialogObserver].
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
if (route is GetDialogRoute) {
showToast("弹窗展示", position: ToastPosition.top);
logD('弹窗展示');
return;
}
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
if (route is GetDialogRoute) {
showToast("弹窗关闭", position: ToastPosition.top);
logD('弹窗关闭');
return;
}
}
@override
void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) =>
logD('didRemove: ${route.str}, previousRoute= ${previousRoute?.str}');
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) =>
logD('didReplace: new= ${newRoute?.str}, old= ${oldRoute?.str}');
@override
void didStartUserGesture(
Route<dynamic> route,
Route<dynamic>? previousRoute,
) =>
logD('didStartUserGesture: ${route.str}, '
'previousRoute= ${previousRoute?.str}');
@override
void didStopUserGesture() => logD('didStopUserGesture');
}
extension on Route<dynamic> {
String get str => 'route(${settings.name}: ${settings.arguments})';
}
Setting NavigatorObserver #
GoRouter(
...
observers: [
GoNavigatorObserver(),
DialogObserver(),
],
...
);
Route redirect #
GoRouter(
...
redirect: (BuildContext context, GoRouterState state) {
logV("redirect: ${state.matchedLocation}");
if (isNotLogin) {
return Routes.login;
}
// no need to redirect at all
return null;
},
...
);
Route exception: not found #
GoRouter(
...
onException: (context, GoRouterState state, GoRouter router) {
router.go(Routes.notFound, extra: {"uri": state.uri.toString()});
},
...
);
Navigation Animation #
enum PageTransitionType {
// 禁用动画
disabled,
// 无动画
none,
// 渐变透明
fade,
// 缩放动画
scale,
// 旋转
rotate,
// 从中间往上下延伸
size,
// 从右到左
right,
// 从左到右
left,
// 从上到下
top,
// 从下到上
bottom,
// 从右到左,加渐变透明
rightFade,
// 从左到右,加渐变透明
leftFade,
}
Define a route with animation by transitionType
property.
goRoute(
path: Paths.none,
child: const TransitionNextPage(),
transitionType: PageTransitionType.none,
),