Skip to content

Commit 2bf6c35

Browse files
committed
[go_router] Fix replace routes hanging the originating completer
1 parent 6e2acf7 commit 2bf6c35

File tree

4 files changed

+96
-2
lines changed

4 files changed

+96
-2
lines changed

packages/go_router/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
## NEXT
1+
## 15.1.3
22

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

57
## 15.1.2

packages/go_router/lib/src/parser.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
168168
return redirectedFuture;
169169
}
170170

171+
/// Ensures the replacement routes can resolve the originating route completer.
172+
Completer<T?>? _createInheritedCompleter<T>(
173+
Completer<T?> current,
174+
RouteMatch lastRouteMatch,
175+
) {
176+
if (lastRouteMatch is ImperativeRouteMatch) {
177+
return _InheritedCompleter<T?>(current, lastRouteMatch.completer);
178+
} else {
179+
return current;
180+
}
181+
}
182+
171183
RouteMatchList _updateRouteMatchList(
172184
RouteMatchList newMatchList, {
173185
required RouteMatchList? baseRouteMatchList,
@@ -185,6 +197,7 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
185197
);
186198
case NavigatingType.pushReplacement:
187199
final RouteMatch routeMatch = baseRouteMatchList!.last;
200+
completer = _createInheritedCompleter(completer!, routeMatch);
188201
baseRouteMatchList = baseRouteMatchList.remove(routeMatch);
189202
if (baseRouteMatchList.isEmpty) {
190203
return newMatchList;
@@ -198,6 +211,7 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
198211
);
199212
case NavigatingType.replace:
200213
final RouteMatch routeMatch = baseRouteMatchList!.last;
214+
completer = _createInheritedCompleter(completer!, routeMatch);
201215
baseRouteMatchList = baseRouteMatchList.remove(routeMatch);
202216
if (baseRouteMatchList.isEmpty) {
203217
return newMatchList;
@@ -224,3 +238,34 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
224238
List<int>.generate(32, (_) => _random.nextInt(33) + 89)));
225239
}
226240
}
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+
}

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: 15.1.2
4+
version: 15.1.3
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/imperative_api_test.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:math';
6+
57
import 'package:flutter/material.dart';
68
import 'package:flutter_test/flutter_test.dart';
79
import 'package:go_router/go_router.dart';
@@ -294,4 +296,49 @@ void main() {
294296
expect(find.text('shell'), findsNothing);
295297
expect(find.byKey(e), findsOneWidget);
296298
});
299+
300+
testWidgets('completer able to be passed on replace',
301+
(WidgetTester tester) async {
302+
final List<RouteBase> routes = <RouteBase>[
303+
GoRoute(
304+
path: '/initial',
305+
builder: (BuildContext context, GoRouterState state) =>
306+
const DummyScreen(),
307+
),
308+
GoRoute(
309+
path: '/intermediate',
310+
builder: (BuildContext context, GoRouterState state) =>
311+
const DummyScreen(),
312+
),
313+
GoRoute(
314+
path: '/final',
315+
builder: (BuildContext context, GoRouterState state) =>
316+
const DummyScreen(),
317+
),
318+
];
319+
320+
final GoRouter router = await createRouter(
321+
routes,
322+
tester,
323+
initialLocation: '/initial',
324+
);
325+
326+
final int random = Random().nextInt(1000);
327+
328+
final Future<Object?> push = router.push('/intermediate');
329+
await tester.pumpAndSettle();
330+
expect(router.state.path, '/intermediate');
331+
332+
final Future<int?> replace = router.replace<int>('/final');
333+
await tester.pumpAndSettle();
334+
expect(router.state.path, '/final');
335+
336+
router.pop(random);
337+
await tester.pumpAndSettle();
338+
expect(router.state.path, '/initial');
339+
await tester.pumpAndSettle();
340+
341+
await expectLater(push, completion(random));
342+
await expectLater(replace, completion(random));
343+
});
297344
}

0 commit comments

Comments
 (0)