Jonny Borges
Committed by GitHub

Merge pull request #2908 from jonataslaw/router_listener

add RouterListener, a widget that can inform its descendants about route changes. Useful for nested navigation, to prevent unnecessary Rebuilds involving RouterOutlet.builder on all widgets.
@@ -8,7 +8,7 @@ FLUTTER_BUILD_NAME=1.0.0 @@ -8,7 +8,7 @@ FLUTTER_BUILD_NAME=1.0.0
8 FLUTTER_BUILD_NUMBER=1 8 FLUTTER_BUILD_NUMBER=1
9 EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386 9 EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386
10 EXCLUDED_ARCHS[sdk=iphoneos*]=armv7 10 EXCLUDED_ARCHS[sdk=iphoneos*]=armv7
11 -DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==,RkxVVFRFUl9XRUJfQ0FOVkFTS0lUX1VSTD1odHRwczovL3d3dy5nc3RhdGljLmNvbS9mbHV0dGVyLWNhbnZhc2tpdC9iMjAxODNlMDQwOTYwOTRiY2MzN2Q5Y2RlMmE0Yjk2ZjVjYzY4NGNmLw== 11 +DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==,RkxVVFRFUl9XRUJfQ0FOVkFTS0lUX1VSTD1odHRwczovL3d3dy5nc3RhdGljLmNvbS9mbHV0dGVyLWNhbnZhc2tpdC9iOGQzNTgxMGU5MWFiOGZjMzliYTVlN2E0MWJmZjZmNjk3ZThlM2E4Lw==
12 DART_OBFUSCATION=false 12 DART_OBFUSCATION=false
13 TRACK_WIDGET_CREATION=true 13 TRACK_WIDGET_CREATION=true
14 TREE_SHAKE_ICONS=false 14 TREE_SHAKE_ICONS=false
@@ -7,7 +7,7 @@ export "FLUTTER_TARGET=/Users/jonatasborges/getx5/getx/example_nav2/lib/main.dar @@ -7,7 +7,7 @@ export "FLUTTER_TARGET=/Users/jonatasborges/getx5/getx/example_nav2/lib/main.dar
7 export "FLUTTER_BUILD_DIR=build" 7 export "FLUTTER_BUILD_DIR=build"
8 export "FLUTTER_BUILD_NAME=1.0.0" 8 export "FLUTTER_BUILD_NAME=1.0.0"
9 export "FLUTTER_BUILD_NUMBER=1" 9 export "FLUTTER_BUILD_NUMBER=1"
10 -export "DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==,RkxVVFRFUl9XRUJfQ0FOVkFTS0lUX1VSTD1odHRwczovL3d3dy5nc3RhdGljLmNvbS9mbHV0dGVyLWNhbnZhc2tpdC9iMjAxODNlMDQwOTYwOTRiY2MzN2Q5Y2RlMmE0Yjk2ZjVjYzY4NGNmLw==" 10 +export "DART_DEFINES=RkxVVFRFUl9XRUJfQVVUT19ERVRFQ1Q9dHJ1ZQ==,RkxVVFRFUl9XRUJfQ0FOVkFTS0lUX1VSTD1odHRwczovL3d3dy5nc3RhdGljLmNvbS9mbHV0dGVyLWNhbnZhc2tpdC9iOGQzNTgxMGU5MWFiOGZjMzliYTVlN2E0MWJmZjZmNjk3ZThlM2E4Lw=="
11 export "DART_OBFUSCATION=false" 11 export "DART_OBFUSCATION=false"
12 export "TRACK_WIDGET_CREATION=true" 12 export "TRACK_WIDGET_CREATION=true"
13 export "TREE_SHAKE_ICONS=false" 13 export "TREE_SHAKE_ICONS=false"
@@ -63,4 +63,3 @@ class HomeView extends GetView<HomeController> { @@ -63,4 +63,3 @@ class HomeView extends GetView<HomeController> {
63 ); 63 );
64 } 64 }
65 } 65 }
66 -  
@@ -37,7 +37,6 @@ class ProfileView extends GetView<ProfileController> { @@ -37,7 +37,6 @@ class ProfileView extends GetView<ProfileController> {
37 child: const Text('Show a test dialog in Home router outlet'), 37 child: const Text('Show a test dialog in Home router outlet'),
38 onPressed: () { 38 onPressed: () {
39 //shows a dialog 39 //shows a dialog
40 -  
41 Get.defaultDialog( 40 Get.defaultDialog(
42 title: 'Test Dialog In Home Outlet !!', 41 title: 'Test Dialog In Home Outlet !!',
43 barrierDismissible: true, 42 barrierDismissible: true,
@@ -10,31 +10,21 @@ class RootView extends GetView<RootController> { @@ -10,31 +10,21 @@ class RootView extends GetView<RootController> {
10 10
11 @override 11 @override
12 Widget build(BuildContext context) { 12 Widget build(BuildContext context) {
13 - return RouterOutlet.builder(  
14 - delegate: Get.nestedKey(null),  
15 - builder: (context) {  
16 - final title = context.location;  
17 - return Scaffold(  
18 - drawer: const DrawerWidget(),  
19 - appBar: AppBar(  
20 - title: Text(title),  
21 - centerTitle: true,  
22 - ),  
23 - //body: HomeView(), 13 + return Scaffold(
  14 + drawer: const DrawerWidget(),
  15 + appBar: AppBar(
  16 + title: RouterListener(builder: (context) {
  17 + final title = context.location;
  18 + return Text(title);
  19 + }),
  20 + centerTitle: true,
  21 + ),
  22 + //body: HomeView(),
24 23
25 - body: GetRouterOutlet(  
26 - initialRoute: Routes.home,  
27 - delegate: Get.nestedKey(null),  
28 - anchorRoute: '/',  
29 - filterPages: (afterAnchor) {  
30 - // print(afterAnchor);  
31 - // print('dddddddddddddddddd');  
32 - // print(afterAnchor.take(1));  
33 - return afterAnchor.take(1);  
34 - },  
35 - ),  
36 - );  
37 - }, 24 + body: GetRouterOutlet(
  25 + initialRoute: Routes.home,
  26 + anchorRoute: '/',
  27 + ),
38 ); 28 );
39 } 29 }
40 } 30 }
@@ -75,6 +75,10 @@ class RouteDecoder { @@ -75,6 +75,10 @@ class RouteDecoder {
75 75
76 @override 76 @override
77 int get hashCode => currentTreeBranch.hashCode ^ pageSettings.hashCode; 77 int get hashCode => currentTreeBranch.hashCode ^ pageSettings.hashCode;
  78 +
  79 + @override
  80 + String toString() =>
  81 + 'RouteDecoder(currentTreeBranch: $currentTreeBranch, pageSettings: $pageSettings)';
78 } 82 }
79 83
80 class ParseRouteTree { 84 class ParseRouteTree {
@@ -84,7 +84,9 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> { @@ -84,7 +84,9 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> {
84 required String initialRoute, 84 required String initialRoute,
85 Iterable<GetPage> Function(Iterable<GetPage> afterAnchor)? filterPages, 85 Iterable<GetPage> Function(Iterable<GetPage> afterAnchor)? filterPages,
86 GetDelegate? delegate, 86 GetDelegate? delegate,
  87 + String? restorationScopeId,
87 }) : this.pickPages( 88 }) : this.pickPages(
  89 + restorationScopeId: restorationScopeId,
88 pickPages: (config) { 90 pickPages: (config) {
89 Iterable<GetPage<dynamic>> ret; 91 Iterable<GetPage<dynamic>> ret;
90 if (anchorRoute == null) { 92 if (anchorRoute == null) {
@@ -114,6 +116,7 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> { @@ -114,6 +116,7 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> {
114 GetPage Function(GetDelegate delegate)? emptyPage, 116 GetPage Function(GetDelegate delegate)? emptyPage,
115 required Iterable<GetPage> Function(RouteDecoder currentNavStack) pickPages, 117 required Iterable<GetPage> Function(RouteDecoder currentNavStack) pickPages,
116 bool Function(Route<dynamic>, dynamic)? onPopPage, 118 bool Function(Route<dynamic>, dynamic)? onPopPage,
  119 + String? restorationScopeId,
117 GlobalKey<NavigatorState>? navigatorKey, 120 GlobalKey<NavigatorState>? navigatorKey,
118 GetDelegate? delegate, 121 GetDelegate? delegate,
119 }) : super( 122 }) : super(
@@ -124,17 +127,22 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> { @@ -124,17 +127,22 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> {
124 ].whereType<GetPage>(); 127 ].whereType<GetPage>();
125 128
126 if (pageRes.isNotEmpty) { 129 if (pageRes.isNotEmpty) {
127 - return GetNavigator(  
128 - onPopPage: onPopPage ??  
129 - (route, result) {  
130 - final didPop = route.didPop(result);  
131 - if (!didPop) {  
132 - return false;  
133 - }  
134 - return true;  
135 - },  
136 - pages: pageRes.toList(),  
137 - key: navigatorKey, 130 + return InheritedNavigator(
  131 + navigatorKey: navigatorKey ??
  132 + Get.rootController.rootDelegate.navigatorKey,
  133 + child: GetNavigator(
  134 + restorationScopeId: restorationScopeId,
  135 + onPopPage: onPopPage ??
  136 + (route, result) {
  137 + final didPop = route.didPop(result);
  138 + if (!didPop) {
  139 + return false;
  140 + }
  141 + return true;
  142 + },
  143 + pages: pageRes.toList(),
  144 + key: navigatorKey,
  145 + ),
138 ); 146 );
139 } 147 }
140 return (emptyWidget?.call(rDelegate) ?? const SizedBox.shrink()); 148 return (emptyWidget?.call(rDelegate) ?? const SizedBox.shrink());
@@ -159,35 +167,44 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> { @@ -159,35 +167,44 @@ class GetRouterOutlet extends RouterOutlet<GetDelegate, RouteDecoder> {
159 ); 167 );
160 } 168 }
161 169
162 -extension PagesListExt on List<GetPage> {  
163 - Iterable<GetPage> pickAtRoute(String route) {  
164 - return skipWhile((value) {  
165 - return value.name != route;  
166 - }); 170 +class InheritedNavigator extends InheritedWidget {
  171 + const InheritedNavigator({
  172 + super.key,
  173 + required super.child,
  174 + required this.navigatorKey,
  175 + });
  176 + final GlobalKey<NavigatorState> navigatorKey;
  177 +
  178 + static InheritedNavigator? of(BuildContext context) {
  179 + return context.dependOnInheritedWidgetOfExactType<InheritedNavigator>();
167 } 180 }
168 181
169 - Iterable<GetPage> pickAfterRoute(String route) {  
170 - return pickAtRoute(route).skip(1); 182 + @override
  183 + bool updateShouldNotify(InheritedNavigator oldWidget) {
  184 + return true;
171 } 185 }
172 } 186 }
173 187
174 -class GetRouterOutletInherited extends InheritedWidget {  
175 - final String anchorRoute;  
176 -  
177 - const GetRouterOutletInherited({  
178 - super.key,  
179 - required this.anchorRoute,  
180 - required Widget child,  
181 - }) : super(child: child); 188 +extension NavKeyExt on BuildContext {
  189 + GlobalKey<NavigatorState>? get parentNavigatorKey {
  190 + return InheritedNavigator.of(this)?.navigatorKey;
  191 + }
  192 +}
182 193
183 - static GetRouterOutletInherited? of(BuildContext context) {  
184 - return context  
185 - .dependOnInheritedWidgetOfExactType<GetRouterOutletInherited>(); 194 +extension PagesListExt on List<GetPage> {
  195 + /// Returns the route and all following routes after the given route.
  196 + Iterable<GetPage> pickFromRoute(String route) {
  197 + return skipWhile((value) => value.name != route);
186 } 198 }
187 199
188 - @override  
189 - bool updateShouldNotify(covariant InheritedWidget oldWidget) {  
190 - return true; 200 + /// Returns the routes after the given route.
  201 + Iterable<GetPage> pickAfterRoute(String route) {
  202 + // If the provided route is root, we take the first route after root.
  203 + if (route == '/') {
  204 + return pickFromRoute(route).skip(1).take(1);
  205 + }
  206 + // Otherwise, we skip the route and take all routes after it.
  207 + return pickFromRoute(route).skip(1);
191 } 208 }
192 } 209 }
193 210
@@ -221,3 +238,93 @@ class IndexedRouteBuilder<T> extends StatelessWidget { @@ -221,3 +238,93 @@ class IndexedRouteBuilder<T> extends StatelessWidget {
221 return builder(context, routes, index); 238 return builder(context, routes, index);
222 } 239 }
223 } 240 }
  241 +
  242 +mixin RouterListenerMixin<T extends StatefulWidget> on State<T> {
  243 + RouterDelegate? delegate;
  244 +
  245 + void _listener() {
  246 + setState(() {});
  247 + }
  248 +
  249 + VoidCallback? disposer;
  250 +
  251 + @override
  252 + void didChangeDependencies() {
  253 + super.didChangeDependencies();
  254 + disposer?.call();
  255 + final router = Router.of(context);
  256 + delegate ??= router.routerDelegate as GetDelegate;
  257 +
  258 + delegate?.addListener(_listener);
  259 + disposer = () => delegate?.removeListener(_listener);
  260 + }
  261 +
  262 + @override
  263 + void dispose() {
  264 + super.dispose();
  265 + disposer?.call();
  266 + }
  267 +}
  268 +
  269 +class RouterListenerInherited extends InheritedWidget {
  270 + const RouterListenerInherited({
  271 + super.key,
  272 + required Widget child,
  273 + }) : super(child: child);
  274 +
  275 + static RouterListenerInherited? of(BuildContext context) {
  276 + return context
  277 + .dependOnInheritedWidgetOfExactType<RouterListenerInherited>();
  278 + }
  279 +
  280 + @override
  281 + bool updateShouldNotify(covariant InheritedWidget oldWidget) {
  282 + return true;
  283 + }
  284 +}
  285 +
  286 +class RouterListener extends StatefulWidget {
  287 + const RouterListener({
  288 + Key? key,
  289 + required this.builder,
  290 + }) : super(key: key);
  291 + final WidgetBuilder builder;
  292 +
  293 + @override
  294 + State<RouterListener> createState() => RouteListenerState();
  295 +}
  296 +
  297 +class RouteListenerState extends State<RouterListener>
  298 + with RouterListenerMixin {
  299 + @override
  300 + Widget build(BuildContext context) {
  301 + return RouterListenerInherited(child: Builder(builder: widget.builder));
  302 + }
  303 +}
  304 +
  305 +class BackButtonCallback extends StatefulWidget {
  306 + const BackButtonCallback({Key? key, required this.builder}) : super(key: key);
  307 + final WidgetBuilder builder;
  308 +
  309 + @override
  310 + State<BackButtonCallback> createState() => RouterListenerState();
  311 +}
  312 +
  313 +class RouterListenerState extends State<BackButtonCallback>
  314 + with RouterListenerMixin {
  315 + late ChildBackButtonDispatcher backButtonDispatcher;
  316 +
  317 + @override
  318 + void didChangeDependencies() {
  319 + super.didChangeDependencies();
  320 + final router = Router.of(context);
  321 + backButtonDispatcher =
  322 + router.backButtonDispatcher!.createChildBackButtonDispatcher();
  323 + }
  324 +
  325 + @override
  326 + Widget build(BuildContext context) {
  327 + backButtonDispatcher.takePriority();
  328 + return widget.builder(context);
  329 + }
  330 +}