Skip to content

[go_router] [shell_route] Add observers parameter #2664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 6.0.7

- Add observers parameter to the ShellRoute that will be passed to the nested Navigator.
- Use `HeroControllerScope` for nested Navigator that fixes Hero Widgets not animating in Nested Navigator.

## 6.0.6

- Adds `reverseTransitionDuration` to `CustomTransitionPage`
Expand Down
38 changes: 34 additions & 4 deletions packages/go_router/lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class RouteBuilder {
_routeMatchLookUp[page];

// final Map<>
/// Caches a HeroController for the nested Navigator, which solves cases where the
/// Hero Widget animation stops working when navigating.
final Map<GlobalKey<NavigatorState>, HeroController> _goHeroCache =
<GlobalKey<NavigatorState>, HeroController>{};

/// Builds the top-level Navigator for the given [RouteMatchList].
Widget build(
Expand Down Expand Up @@ -132,10 +136,10 @@ class RouteBuilder {
bool routerNeglect,
GlobalKey<NavigatorState> navigatorKey,
Map<Page<Object?>, GoRouterState> registry) {
final Map<GlobalKey<NavigatorState>, List<Page<Object?>>> keyToPage =
<GlobalKey<NavigatorState>, List<Page<Object?>>>{};
try {
assert(_routeMatchLookUp.isEmpty);
final Map<GlobalKey<NavigatorState>, List<Page<Object?>>> keyToPage =
<GlobalKey<NavigatorState>, List<Page<Object?>>>{};
_buildRecursive(context, matchList, 0, onPopPage, routerNeglect,
keyToPage, navigatorKey, registry);

Expand All @@ -147,6 +151,10 @@ class RouteBuilder {
return <Page<Object?>>[
_buildErrorPage(context, e, matchList.uri),
];
} finally {
/// Clean up previous cache to prevent memory leak.
_goHeroCache.removeWhere(
(GlobalKey<NavigatorState> key, _) => !keyToPage.keys.contains(key));
}
}

Expand Down Expand Up @@ -191,6 +199,10 @@ class RouteBuilder {
// The key to provide to the ShellRoute's Navigator.
final GlobalKey<NavigatorState> shellNavigatorKey = route.navigatorKey;

// The observers list for the ShellRoute's Navigator.
final List<NavigatorObserver> observers =
route.observers ?? <NavigatorObserver>[];

// Add an entry for the parent navigator if none exists.
keyToPages.putIfAbsent(parentNavigatorKey, () => <Page<Object?>>[]);

Expand All @@ -206,9 +218,15 @@ class RouteBuilder {
_buildRecursive(context, matchList, startIndex + 1, onPopPage,
routerNeglect, keyToPages, shellNavigatorKey, registry);

final HeroController heroController = _goHeroCache.putIfAbsent(
shellNavigatorKey, () => _getHeroController(context));
// Build the Navigator
final Widget child = _buildNavigator(
onPopPage, keyToPages[shellNavigatorKey]!, shellNavigatorKey);
final Widget child = HeroControllerScope(
controller: heroController,
child: _buildNavigator(
onPopPage, keyToPages[shellNavigatorKey]!, shellNavigatorKey,
observers: observers),
);

// Build the Page for this route
final Page<Object?> page =
Expand Down Expand Up @@ -451,6 +469,18 @@ class RouteBuilder {
: _errorBuilderForAppType!(context, state),
);
}

/// Return a HeroController based on the app type.
HeroController _getHeroController(BuildContext context) {
if (context is Element) {
if (isMaterialApp(context)) {
return createMaterialHeroController();
} else if (isCupertinoApp(context)) {
return createCupertinoHeroController();
}
}
return HeroController();
}
}

typedef _PageBuilderForAppType = Page<void> Function({
Expand Down
4 changes: 4 additions & 0 deletions packages/go_router/lib/src/pages/cupertino.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import '../misc/extensions.dart';
bool isCupertinoApp(Element elem) =>
elem.findAncestorWidgetOfExactType<CupertinoApp>() != null;

/// Creates a Cupertino HeroController.
HeroController createCupertinoHeroController() =>
CupertinoApp.createCupertinoHeroController();

/// Builds a Cupertino page.
CupertinoPage<void> pageBuilderForCupertinoApp({
required LocalKey key,
Expand Down
4 changes: 4 additions & 0 deletions packages/go_router/lib/src/pages/material.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import '../misc/extensions.dart';
bool isMaterialApp(Element elem) =>
elem.findAncestorWidgetOfExactType<MaterialApp>() != null;

/// Creates a Material HeroController.
HeroController createMaterialHeroController() =>
MaterialApp.createMaterialHeroController();

/// Builds a Material page.
MaterialPage<void> pageBuilderForMaterialApp({
required LocalKey key,
Expand Down
7 changes: 7 additions & 0 deletions packages/go_router/lib/src/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ class ShellRoute extends RouteBase {
ShellRoute({
this.builder,
this.pageBuilder,
this.observers,
super.routes,
GlobalKey<NavigatorState>? navigatorKey,
}) : assert(routes.isNotEmpty),
Expand Down Expand Up @@ -447,6 +448,12 @@ class ShellRoute extends RouteBase {
/// sub-route's builder.
final ShellRoutePageBuilder? pageBuilder;

/// The observers for a shell route.
///
/// The observers parameter is used by the [Navigator] built for this route.
/// sub-route's observers.
final List<NavigatorObserver>? observers;

/// The [GlobalKey] to be used by the [Navigator] built for this route.
/// All ShellRoutes build a Navigator by default. Child GoRoutes
/// are placed onto this Navigator instead of the root Navigator.
Expand Down
2 changes: 1 addition & 1 deletion packages/go_router/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: go_router
description: A declarative router for Flutter based on Navigation 2 supporting
deep linking, data-driven routes and more
version: 6.0.6
version: 6.0.7
repository: https://github.com/flutter/packages/tree/main/packages/go_router
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22

Expand Down
50 changes: 50 additions & 0 deletions packages/go_router/test/go_router_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3218,6 +3218,56 @@ void main() {
final bool? result = await resultFuture;
expect(result, isTrue);
});

testWidgets('Triggers a Hero inside a ShellRoute',
(WidgetTester tester) async {
final UniqueKey heroKey = UniqueKey();
const String kHeroTag = 'hero';

final List<RouteBase> routes = <RouteBase>[
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return child;
},
routes: <GoRoute>[
GoRoute(
path: '/a',
builder: (BuildContext context, _) {
return Hero(
tag: kHeroTag,
child: Container(),
flightShuttleBuilder: (_, __, ___, ____, _____) {
return Container(key: heroKey);
},
);
}),
GoRoute(
path: '/b',
builder: (BuildContext context, _) {
return Hero(
tag: kHeroTag,
child: Container(),
);
}),
],
)
];
final GoRouter router =
await createRouter(routes, tester, initialLocation: '/a');

// check that flightShuttleBuilder widget is not yet present
expect(find.byKey(heroKey), findsNothing);

// start navigation
router.go('/b');
await tester.pump();
await tester.pump(const Duration(milliseconds: 10));
// check that flightShuttleBuilder widget is visible
expect(find.byKey(heroKey), isOnstage);
// // Waits for the animation finishes.
await tester.pumpAndSettle();
expect(find.byKey(heroKey), findsNothing);
});
});
});
}
28 changes: 28 additions & 0 deletions packages/go_router/test/shell_route_observers_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';

void main() {
test('ShellRoute observers test', () {
final ShellRoute shell = ShellRoute(
observers: <NavigatorObserver>[HeroController()],
builder: (BuildContext context, GoRouterState state, Widget child) {
return SafeArea(child: child);
},
routes: <RouteBase>[
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) {
return Container();
},
),
],
);

expect(shell.observers!.length, 1);
});
}