Skip to content

Commit 199d8c9

Browse files
[go_router] Adds void replace() and replaceNamed to GoRouterDelegate, GoRouter and GoRouterHelper (#2306)
* ✨ Add replace and replaceNamed * ✅ Write a test for replace * ⬆️ 📝 Update version number and changelog * 📝 Improve the documentation * 🎨 Add missing extra line in changelog * ✅ Add a test for replaceNamed
1 parent 9524154 commit 199d8c9

File tree

6 files changed

+186
-1
lines changed

6 files changed

+186
-1
lines changed

packages/go_router/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 4.2.0
2+
3+
- Adds `void replace()` and `replaceNamed` to `GoRouterDelegate`, `GoRouter` and `GoRouterHelper`.
4+
15
## 4.1.1
26

37
- Fixes a bug where calling namedLocation does not support case-insensitive way.

packages/go_router/lib/go_router.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,35 @@ extension GoRouterHelper on BuildContext {
7070
extra: extra,
7171
);
7272

73+
/// Replaces the top-most page of the page stack with the given URL location
74+
/// w/ optional query parameters, e.g. `/family/f2/person/p1?color=blue`.
75+
///
76+
/// See also:
77+
/// * [go] which navigates to the location.
78+
/// * [push] which pushes the location onto the page stack.
79+
void replace(String location, {Object? extra}) =>
80+
GoRouter.of(this).replace(location, extra: extra);
81+
82+
/// Replaces the top-most page of the page stack with the named route w/
83+
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid':
84+
/// 'p1'}`.
85+
///
86+
/// See also:
87+
/// * [goNamed] which navigates a named route.
88+
/// * [pushNamed] which pushes a named route onto the page stack.
89+
void replaceNamed(
90+
String name, {
91+
Map<String, String> params = const <String, String>{},
92+
Map<String, String> queryParams = const <String, String>{},
93+
Object? extra,
94+
}) =>
95+
GoRouter.of(this).replaceNamed(
96+
name,
97+
params: params,
98+
queryParams: queryParams,
99+
extra: extra,
100+
);
101+
73102
/// Returns `true` if there is more than 1 page on the stack.
74103
bool canPop() => GoRouter.of(this).canPop();
75104

packages/go_router/lib/src/go_router.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,41 @@ class GoRouter extends ChangeNotifier with NavigatorObserver {
160160
extra: extra,
161161
);
162162

163+
/// Replaces the top-most page of the page stack with the given URL location
164+
/// w/ optional query parameters, e.g. `/family/f2/person/p1?color=blue`.
165+
///
166+
/// See also:
167+
/// * [go] which navigates to the location.
168+
/// * [push] which pushes the location onto the page stack.
169+
void replace(String location, {Object? extra}) {
170+
routeInformationParser
171+
.parseRouteInformation(
172+
DebugGoRouteInformation(location: location, state: extra),
173+
)
174+
.then<void>((List<GoRouteMatch> matches) {
175+
routerDelegate.replace(matches.last);
176+
});
177+
}
178+
179+
/// Replaces the top-most page of the page stack with the named route w/
180+
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid':
181+
/// 'p1'}`.
182+
///
183+
/// See also:
184+
/// * [goNamed] which navigates a named route.
185+
/// * [pushNamed] which pushes a named route onto the page stack.
186+
void replaceNamed(
187+
String name, {
188+
Map<String, String> params = const <String, String>{},
189+
Map<String, String> queryParams = const <String, String>{},
190+
Object? extra,
191+
}) {
192+
replace(
193+
namedLocation(name, params: params, queryParams: queryParams),
194+
extra: extra,
195+
);
196+
}
197+
163198
/// Returns `true` if there is more than 1 page on the stack.
164199
bool canPop() => routerDelegate.canPop();
165200

packages/go_router/lib/src/go_router_delegate.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ class GoRouterDelegate extends RouterDelegate<List<GoRouteMatch>>
6565
notifyListeners();
6666
}
6767

68+
/// Replaces the top-most page of the page stack with the given one.
69+
///
70+
/// See also:
71+
/// * [push] which pushes the given location onto the page stack.
72+
void replace(GoRouteMatch match) {
73+
_matches.last = match;
74+
notifyListeners();
75+
}
76+
6877
/// Returns `true` if there is more than 1 page on the stack.
6978
bool canPop() {
7079
return _matches.length > 1;

packages/go_router/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: go_router
22
description: A declarative router for Flutter based on Navigation 2 supporting
33
deep linking, data-driven routes and more
4-
version: 4.1.1
4+
version: 4.2.0
55
repository: https://github.com/flutter/packages/tree/main/packages/go_router
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
77

packages/go_router/test/go_router_delegate_test.dart

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,114 @@ void main() {
7878
);
7979
});
8080

81+
group('replace', () {
82+
testWidgets(
83+
'It should replace the last match with the given one',
84+
(WidgetTester tester) async {
85+
final GoRouter goRouter = GoRouter(
86+
initialLocation: '/',
87+
routes: <GoRoute>[
88+
GoRoute(path: '/', builder: (_, __) => const SizedBox()),
89+
GoRoute(path: '/page-0', builder: (_, __) => const SizedBox()),
90+
GoRoute(path: '/page-1', builder: (_, __) => const SizedBox()),
91+
],
92+
);
93+
await tester.pumpWidget(
94+
MaterialApp.router(
95+
routeInformationProvider: goRouter.routeInformationProvider,
96+
routeInformationParser: goRouter.routeInformationParser,
97+
routerDelegate: goRouter.routerDelegate,
98+
),
99+
);
100+
101+
goRouter.push('/page-0');
102+
103+
goRouter.routerDelegate.addListener(expectAsync0(() {}));
104+
final GoRouteMatch first = goRouter.routerDelegate.matches.first;
105+
final GoRouteMatch last = goRouter.routerDelegate.matches.last;
106+
goRouter.replace('/page-1');
107+
expect(goRouter.routerDelegate.matches.length, 2);
108+
expect(
109+
goRouter.routerDelegate.matches.first,
110+
first,
111+
reason: 'The first match should still be in the list of matches',
112+
);
113+
expect(
114+
goRouter.routerDelegate.matches.last,
115+
isNot(last),
116+
reason: 'The last match should have been removed',
117+
);
118+
expect(
119+
goRouter.routerDelegate.matches.last.fullpath,
120+
'/page-1',
121+
reason: 'The new location should have been pushed',
122+
);
123+
},
124+
);
125+
});
126+
127+
group('replaceNamed', () {
128+
testWidgets(
129+
'It should replace the last match with the given one',
130+
(WidgetTester tester) async {
131+
final GoRouter goRouter = GoRouter(
132+
initialLocation: '/',
133+
routes: <GoRoute>[
134+
GoRoute(path: '/', builder: (_, __) => const SizedBox()),
135+
GoRoute(
136+
path: '/page-0',
137+
name: 'page0',
138+
builder: (_, __) => const SizedBox()),
139+
GoRoute(
140+
path: '/page-1',
141+
name: 'page1',
142+
builder: (_, __) => const SizedBox()),
143+
],
144+
);
145+
await tester.pumpWidget(
146+
MaterialApp.router(
147+
routeInformationProvider: goRouter.routeInformationProvider,
148+
routeInformationParser: goRouter.routeInformationParser,
149+
routerDelegate: goRouter.routerDelegate,
150+
),
151+
);
152+
153+
goRouter.pushNamed('page0');
154+
155+
goRouter.routerDelegate.addListener(expectAsync0(() {}));
156+
final GoRouteMatch first = goRouter.routerDelegate.matches.first;
157+
final GoRouteMatch last = goRouter.routerDelegate.matches.last;
158+
goRouter.replaceNamed('page1');
159+
expect(goRouter.routerDelegate.matches.length, 2);
160+
expect(
161+
goRouter.routerDelegate.matches.first,
162+
first,
163+
reason: 'The first match should still be in the list of matches',
164+
);
165+
expect(
166+
goRouter.routerDelegate.matches.last,
167+
isNot(last),
168+
reason: 'The last match should have been removed',
169+
);
170+
expect(
171+
goRouter.routerDelegate.matches.last,
172+
isA<GoRouteMatch>()
173+
.having(
174+
(GoRouteMatch match) => match.fullpath,
175+
'match.fullpath',
176+
'/page-1',
177+
)
178+
.having(
179+
(GoRouteMatch match) => match.route.name,
180+
'match.route.name',
181+
'page1',
182+
),
183+
reason: 'The new location should have been pushed',
184+
);
185+
},
186+
);
187+
});
188+
81189
testWidgets('dispose unsubscribes from refreshListenable',
82190
(WidgetTester tester) async {
83191
final FakeRefreshListenable refreshListenable = FakeRefreshListenable();

0 commit comments

Comments
 (0)