Skip to content

Commit d8ae141

Browse files
committed
[go_router] Add MapRouteResultCallback and PipeRouteCompleterCallback
Both are used to resolve the current route completer when it is replaced. Both are added to `RouteInformationState` and `ImperativeRouteMatch`.
1 parent c5ab57a commit d8ae141

13 files changed

+311
-48
lines changed

packages/go_router/CHANGELOG.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
## 15.3.0
2+
3+
- Resolves an issue where `replace` and `pushReplacement` caused the originating
4+
route's completer to hang by adding `mapReplacementResult` to
5+
`RouteInformationState` and `ImperativeRouteMatch`.
6+
[flutter#141251](https://github.com/flutter/flutter/issues/141251)
7+
18
## 15.2.0
29

310
- `GoRouteData` now defines `.location`, `.go(context)`, `.push(context)`, `.pushReplacement(context)`, and `replace(context)` to be used for [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html). **Requires go_router_builder >= 3.0.0**.
411

512
## 15.1.3
613

7-
* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.
8-
* Fixes typo in API docs.
9-
14+
- Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.
15+
- Fixes typo in API docs.
1016

1117
## 15.1.2
1218

@@ -30,7 +36,7 @@
3036
## 14.8.1
3137

3238
- Secured canPop method for the lack of matches in routerDelegate's configuration.
33-
39+
3440
## 14.8.0
3541

3642
- Adds `preload` parameter to `StatefulShellBranchData.$branch`.
@@ -1194,4 +1200,3 @@
11941200
## 0.1.0
11951201

11961202
- squatting on the package name (I'm not too proud to admit it)
1197-
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
description: This file stores settings for Dart & Flutter DevTools.
2+
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3+
extensions:

packages/go_router/lib/src/configuration.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,12 @@ class RouteConfiguration {
319319
for (final ImperativeRouteMatch imperativeMatch
320320
in matchList.matches.whereType<ImperativeRouteMatch>()) {
321321
final ImperativeRouteMatch match = ImperativeRouteMatch(
322-
pageKey: imperativeMatch.pageKey,
323-
matches: findMatch(imperativeMatch.matches.uri,
324-
extra: imperativeMatch.matches.extra),
325-
completer: imperativeMatch.completer);
322+
pageKey: imperativeMatch.pageKey,
323+
matches: findMatch(imperativeMatch.matches.uri,
324+
extra: imperativeMatch.matches.extra),
325+
completer: imperativeMatch.completer,
326+
pipeCompleter: imperativeMatch.pipeCompleter,
327+
);
326328
result = result.push(match);
327329
}
328330
return result;

packages/go_router/lib/src/information_provider.dart

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ enum NavigatingType {
3636
restore,
3737
}
3838

39+
/// Maps the replacement route result to the correct generic type.
40+
typedef MapRouteResultCallback<T extends Object?> = T Function(Object? result);
41+
42+
/// Pipe completer to the last route match completer.
43+
typedef PipeRouteCompleterCallback = Completer<Next?>
44+
Function<Next extends Object?>(Completer<Next?> next);
45+
3946
/// The data class to be stored in [RouteInformation.state] to be used by
4047
/// [GoRouteInformationParser].
4148
///
@@ -49,9 +56,16 @@ class RouteInformationState<T> {
4956
this.completer,
5057
this.baseRouteMatchList,
5158
required this.type,
59+
MapRouteResultCallback<T?>? mapReplacementResult,
5260
}) : assert((type == NavigatingType.go || type == NavigatingType.restore) ==
5361
(completer == null)),
54-
assert((type != NavigatingType.go) == (baseRouteMatchList != null));
62+
assert((type != NavigatingType.go) == (baseRouteMatchList != null)),
63+
mapReplacementResult =
64+
mapReplacementResult ?? _defaultMapReplacementResult;
65+
66+
static T? _defaultMapReplacementResult<T>(Object? result) {
67+
return null;
68+
}
5569

5670
/// The extra object used when navigating with [GoRouter].
5771
final Object? extra;
@@ -63,13 +77,30 @@ class RouteInformationState<T> {
6377
/// [NavigatingType.restore].
6478
final Completer<T?>? completer;
6579

80+
/// Maps the replacement result to the appropriate type, to resolve the
81+
/// [completer].
82+
final MapRouteResultCallback<T?> mapReplacementResult;
83+
6684
/// The base route match list to push on top to.
6785
///
6886
/// This is only null if [type] is [NavigatingType.go].
6987
final RouteMatchList? baseRouteMatchList;
7088

7189
/// The type of navigation.
7290
final NavigatingType type;
91+
92+
/// Pipes the completer to the next completer in the chain.
93+
Completer<Next?> pipeCompleter<Next extends Object?>(Completer<Next?> next) {
94+
if (completer == null) {
95+
return next;
96+
}
97+
98+
return _PipeCompleter<T?, Next?>(
99+
next: next,
100+
current: completer!,
101+
mapReplacementResult: mapReplacementResult,
102+
);
103+
}
73104
}
74105

75106
/// The [RouteInformationProvider] created by go_router.
@@ -156,8 +187,12 @@ class GoRouteInformationProvider extends RouteInformationProvider
156187
}
157188

158189
/// Pushes the `location` as a new route on top of `base`.
159-
Future<T?> push<T>(String location,
160-
{required RouteMatchList base, Object? extra}) {
190+
Future<T?> push<T>(
191+
String location, {
192+
required RouteMatchList base,
193+
Object? extra,
194+
MapRouteResultCallback<T?>? mapReplacementResult,
195+
}) {
161196
final Completer<T?> completer = Completer<T?>();
162197
_setValue(
163198
location,
@@ -166,6 +201,7 @@ class GoRouteInformationProvider extends RouteInformationProvider
166201
baseRouteMatchList: base,
167202
completer: completer,
168203
type: NavigatingType.push,
204+
mapReplacementResult: mapReplacementResult,
169205
),
170206
);
171207
return completer.future;
@@ -196,8 +232,12 @@ class GoRouteInformationProvider extends RouteInformationProvider
196232

197233
/// Removes the top-most route match from `base` and pushes the `location` as a
198234
/// new route on top.
199-
Future<T?> pushReplacement<T>(String location,
200-
{required RouteMatchList base, Object? extra}) {
235+
Future<T?> pushReplacement<T>(
236+
String location, {
237+
required RouteMatchList base,
238+
Object? extra,
239+
MapRouteResultCallback<T?>? mapReplacementResult,
240+
}) {
201241
final Completer<T?> completer = Completer<T?>();
202242
_setValue(
203243
location,
@@ -206,14 +246,19 @@ class GoRouteInformationProvider extends RouteInformationProvider
206246
baseRouteMatchList: base,
207247
completer: completer,
208248
type: NavigatingType.pushReplacement,
249+
mapReplacementResult: mapReplacementResult,
209250
),
210251
);
211252
return completer.future;
212253
}
213254

214255
/// Replaces the top-most route match from `base` with the `location`.
215-
Future<T?> replace<T>(String location,
216-
{required RouteMatchList base, Object? extra}) {
256+
Future<T?> replace<T>(
257+
String location, {
258+
required RouteMatchList base,
259+
Object? extra,
260+
MapRouteResultCallback<T?>? mapReplacementResult,
261+
}) {
217262
final Completer<T?> completer = Completer<T?>();
218263
_setValue(
219264
location,
@@ -290,3 +335,42 @@ class GoRouteInformationProvider extends RouteInformationProvider
290335
return SynchronousFuture<bool>(true);
291336
}
292337
}
338+
339+
/// Ensures the replacement routes can resolve the originating route completer.
340+
/// Mainly used by [RouteInformationState.pipeCompleter]
341+
class _PipeCompleter<Current extends Object?, Next extends Object?>
342+
implements Completer<Next> {
343+
_PipeCompleter({
344+
required Completer<Next> next,
345+
required Completer<Current> current,
346+
required MapRouteResultCallback<Current> mapReplacementResult,
347+
}) : _next = next,
348+
_current = current,
349+
_mapReplacementResult = mapReplacementResult;
350+
351+
final Completer<Next> _next;
352+
final Completer<Current> _current;
353+
final MapRouteResultCallback<Current> _mapReplacementResult;
354+
355+
@override
356+
void complete([FutureOr<Next>? value]) {
357+
_next.complete(value);
358+
if (!_current.isCompleted) {
359+
_current.complete(_mapReplacementResult(value));
360+
}
361+
}
362+
363+
@override
364+
void completeError(Object error, [StackTrace? stackTrace]) {
365+
_next.completeError(error, stackTrace);
366+
if (!_current.isCompleted) {
367+
_current.completeError(error, stackTrace);
368+
}
369+
}
370+
371+
@override
372+
Future<Next> get future => _next.future;
373+
374+
@override
375+
bool get isCompleted => _next.isCompleted;
376+
}

packages/go_router/lib/src/match.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:logging/logging.dart';
1212
import 'package:meta/meta.dart';
1313

1414
import 'configuration.dart';
15+
import 'information_provider.dart';
1516
import 'logging.dart';
1617
import 'misc/errors.dart';
1718
import 'path_utils.dart';
@@ -432,9 +433,12 @@ class ShellRouteMatch extends RouteMatchBase {
432433
/// The route match that represent route pushed through [GoRouter.push].
433434
class ImperativeRouteMatch extends RouteMatch {
434435
/// Constructor for [ImperativeRouteMatch].
435-
ImperativeRouteMatch(
436-
{required super.pageKey, required this.matches, required this.completer})
437-
: super(
436+
ImperativeRouteMatch({
437+
required super.pageKey,
438+
required this.matches,
439+
required this.completer,
440+
required this.pipeCompleter,
441+
}) : super(
438442
route: _getsLastRouteFromMatches(matches),
439443
matchedLocation: _getsMatchedLocationFromMatches(matches),
440444
);
@@ -460,6 +464,9 @@ class ImperativeRouteMatch extends RouteMatch {
460464
/// The completer for the future returned by [GoRouter.push].
461465
final Completer<Object?> completer;
462466

467+
/// Pipes the completer to the next completer in the chain.
468+
final PipeRouteCompleterCallback pipeCompleter;
469+
463470
/// Called when the corresponding [Route] associated with this route match is
464471
/// completed.
465472
void complete([dynamic value]) {
@@ -967,6 +974,8 @@ class _RouteMatchListDecoder
967974
// https://github.com/flutter/flutter/issues/128122.
968975
completer: Completer<Object?>(),
969976
matches: imperativeMatchList,
977+
// TODO(nouvist): Sorry, I don't know what convert does.
978+
pipeCompleter: <Next>(Completer<Next?> next) => next,
970979
);
971980
matchList = matchList.push(imperativeMatch);
972981
}

packages/go_router/lib/src/parser.dart

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
117117
baseRouteMatchList: state.baseRouteMatchList,
118118
completer: state.completer,
119119
type: state.type,
120+
pipeCompleter: state.pipeCompleter,
120121
);
121122
});
122123
}
@@ -168,11 +169,24 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
168169
return redirectedFuture;
169170
}
170171

172+
/// Ensures the replacement routes can resolve the originating route completer.
173+
Completer<T?> _pipeCompleter<T extends Object?>(
174+
Completer<T?> currentCompleter,
175+
RouteMatch lastRouteMatch,
176+
) {
177+
if (lastRouteMatch is ImperativeRouteMatch) {
178+
return lastRouteMatch.pipeCompleter(currentCompleter);
179+
} else {
180+
return currentCompleter;
181+
}
182+
}
183+
171184
RouteMatchList _updateRouteMatchList(
172185
RouteMatchList newMatchList, {
173186
required RouteMatchList? baseRouteMatchList,
174187
required Completer<Object?>? completer,
175188
required NavigatingType type,
189+
required PipeRouteCompleterCallback pipeCompleter,
176190
}) {
177191
switch (type) {
178192
case NavigatingType.push:
@@ -181,32 +195,37 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
181195
pageKey: _getUniqueValueKey(),
182196
completer: completer!,
183197
matches: newMatchList,
198+
pipeCompleter: pipeCompleter,
184199
),
185200
);
186201
case NavigatingType.pushReplacement:
187202
final RouteMatch routeMatch = baseRouteMatchList!.last;
203+
completer = _pipeCompleter(completer!, routeMatch);
188204
baseRouteMatchList = baseRouteMatchList.remove(routeMatch);
189205
if (baseRouteMatchList.isEmpty) {
190206
return newMatchList;
191207
}
192208
return baseRouteMatchList.push(
193209
ImperativeRouteMatch(
194210
pageKey: _getUniqueValueKey(),
195-
completer: completer!,
211+
completer: completer,
196212
matches: newMatchList,
213+
pipeCompleter: pipeCompleter,
197214
),
198215
);
199216
case NavigatingType.replace:
200217
final RouteMatch routeMatch = baseRouteMatchList!.last;
218+
completer = _pipeCompleter(completer!, routeMatch);
201219
baseRouteMatchList = baseRouteMatchList.remove(routeMatch);
202220
if (baseRouteMatchList.isEmpty) {
203221
return newMatchList;
204222
}
205223
return baseRouteMatchList.push(
206224
ImperativeRouteMatch(
207225
pageKey: routeMatch.pageKey,
208-
completer: completer!,
226+
completer: completer,
209227
matches: newMatchList,
228+
pipeCompleter: pipeCompleter,
210229
),
211230
);
212231
case NavigatingType.go:

0 commit comments

Comments
 (0)