admin管理员组文章数量:1316399
I need some of my app's pages to be notified when they are activated or deactivated as a result of change of the current Navigator
. When both my target page (PageA) and the overlapping page (PageB) belong to the same Navigator
, I can attach a RouteObserver
to the Navigator
and implement the RouteAware
interface in the PageAState class to trigger the didPushNext
and didPopNext
callbacks.
Unfortunately, this approach does not work when PageA belongs to a local navigator while PageB is part of the root navigator. If PageB appears over PageA, neither of the navigators triggers the didPushNext
or didPopNext
callbacks for PageAState.
Just to make things clear, here is the simplified version of my router's configuration:
final routerConfig = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/',
observers: [rootRouteObserver],
routes: [
GoRoute(
path: '/',
redirect: (context, state) => AppRoutes.splash(),
),
StatefulShellRoute.indexedStack(
builder:
(BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) {
return ScaffoldWithNavigationBar(navigationShell: navigationShell);
},
branches: <StatefulShellBranch>[
StatefulShellBranch(
navigatorKey: _miscNavigatorKey,
observers: [miscRouteObserver],
routes: <RouteBase>[
GoRoute(
path: AppRoutes.misc(),
builder: (context, state) => const MiscPage(),
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _rootNavigatorKey,
path: AppRoutes._ordersRouteName,
builder: (context, state) => const OrdersPage(),
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _rootNavigatorKey,
path: '${AppRoutes._orderRouteName}/:orderId',
builder: (context, state) =>
SingleOrderPage(orderId: state.pathParameters['orderId']),
),
],
),
],
),
],
),
],
),
],
);
As you can see, MiscPage
and OrdersPage
belong to different navigators (identified by _miscNavigatorKey
and _rootNavigatorKey
respectively). MiscPage
is displayed inside a shell route with tabbed navigation and represents a root page of one the tabs. OrdersPage
appears above it and is designed to be shown fullscreen, overlapping both MiscPage
and the tab bar.
I have the same the same behaviour implemented for both MiscPage
and OrdersPage
:
class AnyPageState extends State<AnyPage> with RouteAware {
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
rootRouteObserver.subscribe(this, ModalRoute.of(context)!);
miscRouteObserver.subscribe(this, ModalRoute.of(context)!);
}
);
super.initState();
}
@override
void dispose() {
rootRouteObserver.unsubscribe(this);
miscRouteObserver.unsubscribe(this);
super.dispose();
}
@override
void didPopNext() {
doSomething();
}
}
So, here I'm subscribed to both route observers and expecting for didPopNext
to be called upon OrdersPage
activates again after closing SingleOrderPage
and MiscPage
- after closing OrdersPage
. IRL didPopNext
is called for OrdersPage
only - IMO, because both OrdersPage
and SingleOrderPage
have the same (root) navigator. MiscPage
and OrdersPage
have different navigators, so didPopNext
is never called for the first page.
Back to my question, is there any other way in GoRouter, Navigator, Flutter, etc. to get the job done in this case?
I need some of my app's pages to be notified when they are activated or deactivated as a result of change of the current Navigator
. When both my target page (PageA) and the overlapping page (PageB) belong to the same Navigator
, I can attach a RouteObserver
to the Navigator
and implement the RouteAware
interface in the PageAState class to trigger the didPushNext
and didPopNext
callbacks.
Unfortunately, this approach does not work when PageA belongs to a local navigator while PageB is part of the root navigator. If PageB appears over PageA, neither of the navigators triggers the didPushNext
or didPopNext
callbacks for PageAState.
Just to make things clear, here is the simplified version of my router's configuration:
final routerConfig = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/',
observers: [rootRouteObserver],
routes: [
GoRoute(
path: '/',
redirect: (context, state) => AppRoutes.splash(),
),
StatefulShellRoute.indexedStack(
builder:
(BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) {
return ScaffoldWithNavigationBar(navigationShell: navigationShell);
},
branches: <StatefulShellBranch>[
StatefulShellBranch(
navigatorKey: _miscNavigatorKey,
observers: [miscRouteObserver],
routes: <RouteBase>[
GoRoute(
path: AppRoutes.misc(),
builder: (context, state) => const MiscPage(),
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _rootNavigatorKey,
path: AppRoutes._ordersRouteName,
builder: (context, state) => const OrdersPage(),
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _rootNavigatorKey,
path: '${AppRoutes._orderRouteName}/:orderId',
builder: (context, state) =>
SingleOrderPage(orderId: state.pathParameters['orderId']),
),
],
),
],
),
],
),
],
),
],
);
As you can see, MiscPage
and OrdersPage
belong to different navigators (identified by _miscNavigatorKey
and _rootNavigatorKey
respectively). MiscPage
is displayed inside a shell route with tabbed navigation and represents a root page of one the tabs. OrdersPage
appears above it and is designed to be shown fullscreen, overlapping both MiscPage
and the tab bar.
I have the same the same behaviour implemented for both MiscPage
and OrdersPage
:
class AnyPageState extends State<AnyPage> with RouteAware {
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(
(_) {
rootRouteObserver.subscribe(this, ModalRoute.of(context)!);
miscRouteObserver.subscribe(this, ModalRoute.of(context)!);
}
);
super.initState();
}
@override
void dispose() {
rootRouteObserver.unsubscribe(this);
miscRouteObserver.unsubscribe(this);
super.dispose();
}
@override
void didPopNext() {
doSomething();
}
}
So, here I'm subscribed to both route observers and expecting for didPopNext
to be called upon OrdersPage
activates again after closing SingleOrderPage
and MiscPage
- after closing OrdersPage
. IRL didPopNext
is called for OrdersPage
only - IMO, because both OrdersPage
and SingleOrderPage
have the same (root) navigator. MiscPage
and OrdersPage
have different navigators, so didPopNext
is never called for the first page.
Back to my question, is there any other way in GoRouter, Navigator, Flutter, etc. to get the job done in this case?
Share Improve this question edited Feb 2 at 16:25 Ken White 126k15 gold badges236 silver badges464 bronze badges asked Jan 29 at 19:33 Vyacheslav OrlovskyVyacheslav Orlovsky 4361 gold badge7 silver badges18 bronze badges1 Answer
Reset to default 0I could not find any built-in solution, so have to propose my own one:
- Create a class that holds references to all
RouteObserver
instances used in router config and mediates subscriptions. It also holds information to matchGoRoute
andNavigator
'sRoute
of the subscribed pages
class _DummyRoute extends Route {}
class _MultiRouteObserver {
static final _dummyRoute = _DummyRoute();
final _observers = <RouteObserver>{};
final _routesMap = <RouteBase, Route>{};
RouteObserver add([RouteObserver? observer]) {
observer ??= RouteObserver();
_observers.add(observer);
return observer;
}
void subscribe(RouteAware routeAware, {required Route route, RouteBase? goRoute}) {
if (goRoute != null) {
_routesMap[goRoute] = route;
final navigator = goRoute.parentNavigatorKey!.currentWidget as Navigator;
RouteObserver? observer = navigator.observers.firstWhereOrNull(
(element) => _observers.contains(element),
) as RouteObserver?;
observer?.subscribe(routeAware, route);
} else {
for (RouteObserver observer in _observers) {
observer.subscribe(routeAware, route);
}
}
}
void unsubscribe(RouteAware routeAware, {RouteBase? goRoute}) {
if (goRoute != null) {
_routesMap.remove(goRoute);
}
for (RouteObserver observer in _observers) {
observer.unsubscribe(routeAware);
}
}
void didPopNextFor(RouteBase goRoute) {
for (RouteObserver observer in _observers) {
observer.didPop(_dummyRoute, _routesMap[goRoute]);
}
}
void didPushNextFor(RouteBase goRoute) {
for (RouteObserver observer in _observers) {
observer.didPush(_dummyRoute, _routesMap[goRoute]);
}
}
}
final appRouteObserver = _MultiRouteObserver();
- Update router config, pass a
RouteObserver
instance created withappRouteObserver.add()
and explicitly specify an actualparentNavigatorKey
for eachGoRoute
final routerConfig = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/',
observers: [appRouteObserver.add()],
routes: [
StatefulShellRoute.indexedStack(
parentNavigatorKey: _rootNavigatorKey,
builder:
(BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) {
return ScaffoldWithNavigationBar(navigationShell: navigationShell);
},
branches: <StatefulShellBranch>[
StatefulShellBranch(
navigatorKey: _catalogueNavigatorKey,
observers: [appRouteObserver.add()],
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _catalogueNavigatorKey,
path: AppRoutes.catalogue(),
builder: (context, state) => const CataloguePage(),
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _catalogueNavigatorKey,
path: '${AppRoutes._categoryRouteName}/:categoryId',
builder: (context, state) => CategoryPage(
categoryId: int.parse(state.pathParameters['categoryId'] ?? '0')),
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _rootNavigatorKey,
path: '${AppRoutes._productRouteName}/:productId',
builder: (context, state) => ProductPage(
productId: int.parse(state.pathParameters['productId'] ?? '0')),
),
],
),
],
),
],
),
StatefulShellBranch(
navigatorKey: _miscNavigatorKey,
observers: [appRouteObserver.add()],
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _miscNavigatorKey,
path: AppRoutes.misc(),
redirect: _loggedInOnly,
builder: (context, state) => const MiscPage(),
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _rootNavigatorKey,
path: AppRoutes._userProfileRouteName,
builder: (context, state) => const UserProfilePage(),
),
GoRoute(
parentNavigatorKey: _rootNavigatorKey,
path: AppRoutes._ordersRouteName,
builder: (context, state) => const OrdersPage(),
routes: <RouteBase>[
GoRoute(
parentNavigatorKey: _rootNavigatorKey,
path: '${AppRoutes._orderRouteName}/:orderId',
builder: (context, state) =>
OrderPage(orderId: state.pathParameters['orderId']),
),
],
),
],
),
],
),
],
),
],
);
- Create a mixin that implements subscription to any
RouteObserver
instance through commonRouteAware
interface. From this pointdidPop
,didPopNext
, etc. will be called for any page containing this mixin.
mixin AppRouteObserverSubscription<T extends StatefulWidget> on State<T>, RouteAware {
GoRoute? goRoute;
@override
void initState() {
goRoute = GoRouter.of(context).state?.topRoute;
WidgetsBinding.instance.addPostFrameCallback(
(_) => appRouteObserver.subscribe(
this,
route: ModalRoute.of(context)!,
goRoute: goRoute,
),
);
super.initState();
}
@override
void dispose() {
appRouteObserver.unsubscribe(this, goRoute: goRoute);
super.dispose();
}
}
- Add the following actions to the
ScaffoldWithNavigationBarState
(theAppRouteObserverSubscription
mixin has not been applied as goRoute's value is obtained there in a unique way). This code actually makesdidPopNext
anddidPushNext
to be called for the topmost page inside the router'sStatefulShellRoute
in case of any other page that belongs to rootNavigator
appears over it
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(
(_) => appRouteObserver.subscribe(
this,
route: ModalRoute.of(context)!,
goRoute: widget.navigationShell.route,
),
);
super.initState();
}
@override
void dispose() {
appRouteObserver.unsubscribe(this, goRoute: widget.navigationShell.route);
super.dispose();
}
@override
void didPopNext() {
final topRoute = GoRouter.of(context).state?.topRoute;
if (topRoute != null) {
appRouteObserver.didPopNextFor(topRoute);
}
}
@override
void didPushNext() {
final topRoute = GoRouter.of(context).state?.topRoute;
if (topRoute != null) {
appRouteObserver.didPushNextFor(topRoute);
}
}
本文标签:
版权声明:本文标题:flutter - get Route's Page notified about own activationdeactivation upon changing current Navigator - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742000922a2410995.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论