Skip to content

Commit f8f51c6

Browse files
committed
[go_router] Add MapRouteResultCallback and PipeRouteCompleterCallback
Both are used to resolve the current route completer when it's replaced, and both are added into `RouteInformationState` and `ImperativeRouteMatch`
1 parent 2bf6c35 commit f8f51c6

12 files changed

+272
-108
lines changed

packages/go_router/CHANGELOG.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
## 15.1.3
1+
## 15.2.0
22

3-
* Fixes an issue where `replace` and `pushReplacement` caused the originating route's completer to hang.
3+
- Adds `mapReplacementResult` to `RouteInformationState` and
4+
`ImperativeRouteMatch` to resolve an issue where `replace` and
5+
`pushReplacement` caused the originating route's completer to hang.
46
[flutter#141251](https://github.com/flutter/flutter/issues/141251)
5-
* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.
7+
- Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.
68

79
## 15.1.2
810

@@ -26,7 +28,7 @@
2628
## 14.8.1
2729

2830
- Secured canPop method for the lack of matches in routerDelegate's configuration.
29-
31+
3032
## 14.8.0
3133

3234
- Adds `preload` parameter to `StatefulShellBranchData.$branch`.
@@ -1190,4 +1192,3 @@
11901192
## 0.1.0
11911193

11921194
- squatting on the package name (I'm not too proud to admit it)
1193-

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: 13 additions & 39 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
}
@@ -169,14 +170,14 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
169170
}
170171

171172
/// Ensures the replacement routes can resolve the originating route completer.
172-
Completer<T?>? _createInheritedCompleter<T>(
173-
Completer<T?> current,
173+
Completer<T?> _pipeCompleter<T extends Object?>(
174+
Completer<T?> currentCompleter,
174175
RouteMatch lastRouteMatch,
175176
) {
176177
if (lastRouteMatch is ImperativeRouteMatch) {
177-
return _InheritedCompleter<T?>(current, lastRouteMatch.completer);
178+
return lastRouteMatch.pipeCompleter(currentCompleter);
178179
} else {
179-
return current;
180+
return currentCompleter;
180181
}
181182
}
182183

@@ -185,6 +186,7 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
185186
required RouteMatchList? baseRouteMatchList,
186187
required Completer<Object?>? completer,
187188
required NavigatingType type,
189+
required PipeRouteCompleterCallback pipeCompleter,
188190
}) {
189191
switch (type) {
190192
case NavigatingType.push:
@@ -193,34 +195,37 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
193195
pageKey: _getUniqueValueKey(),
194196
completer: completer!,
195197
matches: newMatchList,
198+
pipeCompleter: pipeCompleter,
196199
),
197200
);
198201
case NavigatingType.pushReplacement:
199202
final RouteMatch routeMatch = baseRouteMatchList!.last;
200-
completer = _createInheritedCompleter(completer!, routeMatch);
203+
completer = _pipeCompleter(completer!, routeMatch);
201204
baseRouteMatchList = baseRouteMatchList.remove(routeMatch);
202205
if (baseRouteMatchList.isEmpty) {
203206
return newMatchList;
204207
}
205208
return baseRouteMatchList.push(
206209
ImperativeRouteMatch(
207210
pageKey: _getUniqueValueKey(),
208-
completer: completer!,
211+
completer: completer,
209212
matches: newMatchList,
213+
pipeCompleter: pipeCompleter,
210214
),
211215
);
212216
case NavigatingType.replace:
213217
final RouteMatch routeMatch = baseRouteMatchList!.last;
214-
completer = _createInheritedCompleter(completer!, routeMatch);
218+
completer = _pipeCompleter(completer!, routeMatch);
215219
baseRouteMatchList = baseRouteMatchList.remove(routeMatch);
216220
if (baseRouteMatchList.isEmpty) {
217221
return newMatchList;
218222
}
219223
return baseRouteMatchList.push(
220224
ImperativeRouteMatch(
221225
pageKey: routeMatch.pageKey,
222-
completer: completer!,
226+
completer: completer,
223227
matches: newMatchList,
228+
pipeCompleter: pipeCompleter,
224229
),
225230
);
226231
case NavigatingType.go:
@@ -238,34 +243,3 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
238243
List<int>.generate(32, (_) => _random.nextInt(33) + 89)));
239244
}
240245
}
241-
242-
/// Ensures the replacement routes can resolve the originating route completer.
243-
/// Mainly used by [GoRouteInformationParser._createInheritedCompleter].
244-
class _InheritedCompleter<T extends Object?> implements Completer<T> {
245-
_InheritedCompleter(this._current, this._last);
246-
247-
final Completer<T> _current;
248-
final Completer<Object?> _last;
249-
250-
@override
251-
void complete([FutureOr<T>? value]) {
252-
_current.complete(value);
253-
if (!_last.isCompleted) {
254-
_last.complete(value);
255-
}
256-
}
257-
258-
@override
259-
void completeError(Object error, [StackTrace? stackTrace]) {
260-
_current.completeError(error, stackTrace);
261-
if (!_last.isCompleted) {
262-
_last.completeError(error, stackTrace);
263-
}
264-
}
265-
266-
@override
267-
Future<T> get future => _current.future;
268-
269-
@override
270-
bool get isCompleted => _current.isCompleted;
271-
}

0 commit comments

Comments
 (0)