Skip to content
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
4 changes: 4 additions & 0 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.2.0

- Adds `void replace()` and `replaceNamed` to `GoRouterDelegate`, `GoRouter` and `GoRouterHelper`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add an empty line between two versions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops sorry for that, I added that in 8ec9776


## 4.1.1

- Fixes a bug where calling namedLocation does not support case-insensitive way.
Expand Down
29 changes: 29 additions & 0 deletions packages/go_router/lib/go_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,35 @@ extension GoRouterHelper on BuildContext {
extra: extra,
);

/// Replaces the top-most page of the page stack with the given URL location
/// w/ optional query parameters, e.g. `/family/f2/person/p1?color=blue`.
///
/// See also:
/// * [go] which navigates to the location.
/// * [push] which pushes the location onto the page stack.
void replace(String location, {Object? extra}) =>
GoRouter.of(this).replace(location, extra: extra);

/// Replaces the top-most page of the page stack with the named route w/
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid':
/// 'p1'}`.
///
/// See also:
/// * [goNamed] which navigates a named route.
/// * [pushNamed] which pushes a named route onto the page stack.
void replaceNamed(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, String> queryParams = const <String, String>{},
Object? extra,
}) =>
GoRouter.of(this).replaceNamed(
name,
params: params,
queryParams: queryParams,
extra: extra,
);

/// Returns `true` if there is more than 1 page on the stack.
bool canPop() => GoRouter.of(this).canPop();

Expand Down
35 changes: 35 additions & 0 deletions packages/go_router/lib/src/go_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,41 @@ class GoRouter extends ChangeNotifier with NavigatorObserver {
extra: extra,
);

/// Replaces the top-most page of the page stack with the given URL location
/// w/ optional query parameters, e.g. `/family/f2/person/p1?color=blue`.
///
/// See also:
/// * [go] which navigates to the location.
/// * [push] which pushes the location onto the page stack.
void replace(String location, {Object? extra}) {
routeInformationParser
.parseRouteInformation(
DebugGoRouteInformation(location: location, state: extra),
)
.then<void>((List<GoRouteMatch> matches) {
routerDelegate.replace(matches.last);
});
}

/// Replaces the top-most page of the page stack with the named route w/
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid':
/// 'p1'}`.
///
/// See also:
/// * [goNamed] which navigates a named route.
/// * [pushNamed] which pushes a named route onto the page stack.
void replaceNamed(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, String> queryParams = const <String, String>{},
Object? extra,
}) {
replace(
namedLocation(name, params: params, queryParams: queryParams),
extra: extra,
);
}

/// Returns `true` if there is more than 1 page on the stack.
bool canPop() => routerDelegate.canPop();

Expand Down
9 changes: 9 additions & 0 deletions packages/go_router/lib/src/go_router_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ class GoRouterDelegate extends RouterDelegate<List<GoRouteMatch>>
notifyListeners();
}

/// Replaces the top-most page of the page stack with the given one.
///
/// See also:
/// * [push] which pushes the given location onto the page stack.
void replace(GoRouteMatch match) {
_matches.last = match;
notifyListeners();
}

/// Returns `true` if there is more than 1 page on the stack.
bool canPop() {
return _matches.length > 1;
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: 4.1.1
version: 4.2.0
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
108 changes: 108 additions & 0 deletions packages/go_router/test/go_router_delegate_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,114 @@ void main() {
);
});

group('replace', () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider adding a test for replaceNamed too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test for replaceNamed in 44e35ef Tell me what you think about it

testWidgets(
'It should replace the last match with the given one',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/',
routes: <GoRoute>[
GoRoute(path: '/', builder: (_, __) => const SizedBox()),
GoRoute(path: '/page-0', builder: (_, __) => const SizedBox()),
GoRoute(path: '/page-1', builder: (_, __) => const SizedBox()),
],
);
await tester.pumpWidget(
MaterialApp.router(
routeInformationProvider: goRouter.routeInformationProvider,
routeInformationParser: goRouter.routeInformationParser,
routerDelegate: goRouter.routerDelegate,
),
);

goRouter.push('/page-0');

goRouter.routerDelegate.addListener(expectAsync0(() {}));
final GoRouteMatch first = goRouter.routerDelegate.matches.first;
final GoRouteMatch last = goRouter.routerDelegate.matches.last;
goRouter.replace('/page-1');
expect(goRouter.routerDelegate.matches.length, 2);
expect(
goRouter.routerDelegate.matches.first,
first,
reason: 'The first match should still be in the list of matches',
);
expect(
goRouter.routerDelegate.matches.last,
isNot(last),
reason: 'The last match should have been removed',
);
expect(
goRouter.routerDelegate.matches.last.fullpath,
'/page-1',
reason: 'The new location should have been pushed',
);
},
);
});

group('replaceNamed', () {
testWidgets(
'It should replace the last match with the given one',
(WidgetTester tester) async {
final GoRouter goRouter = GoRouter(
initialLocation: '/',
routes: <GoRoute>[
GoRoute(path: '/', builder: (_, __) => const SizedBox()),
GoRoute(
path: '/page-0',
name: 'page0',
builder: (_, __) => const SizedBox()),
GoRoute(
path: '/page-1',
name: 'page1',
builder: (_, __) => const SizedBox()),
],
);
await tester.pumpWidget(
MaterialApp.router(
routeInformationProvider: goRouter.routeInformationProvider,
routeInformationParser: goRouter.routeInformationParser,
routerDelegate: goRouter.routerDelegate,
),
);

goRouter.pushNamed('page0');

goRouter.routerDelegate.addListener(expectAsync0(() {}));
final GoRouteMatch first = goRouter.routerDelegate.matches.first;
final GoRouteMatch last = goRouter.routerDelegate.matches.last;
goRouter.replaceNamed('page1');
expect(goRouter.routerDelegate.matches.length, 2);
expect(
goRouter.routerDelegate.matches.first,
first,
reason: 'The first match should still be in the list of matches',
);
expect(
goRouter.routerDelegate.matches.last,
isNot(last),
reason: 'The last match should have been removed',
);
expect(
goRouter.routerDelegate.matches.last,
isA<GoRouteMatch>()
.having(
(GoRouteMatch match) => match.fullpath,
'match.fullpath',
'/page-1',
)
.having(
(GoRouteMatch match) => match.route.name,
'match.route.name',
'page1',
),
reason: 'The new location should have been pushed',
);
},
);
});

testWidgets('dispose unsubscribes from refreshListenable',
(WidgetTester tester) async {
final FakeRefreshListenable refreshListenable = FakeRefreshListenable();
Expand Down