-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[go_router] Fix replace routes hanging the originating completer #9332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
description: This file stores settings for Dart & Flutter DevTools. | ||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states | ||
extensions: |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,13 @@ enum NavigatingType { | |
restore, | ||
} | ||
|
||
/// Maps the replacement route result to the correct generic type. | ||
typedef MapRouteResultCallback<T extends Object?> = T Function(Object? result); | ||
|
||
/// Pipe completer to the last route match completer. | ||
typedef PipeRouteCompleterCallback = Completer<Next?> | ||
Function<Next extends Object?>(Completer<Next?> next); | ||
|
||
/// The data class to be stored in [RouteInformation.state] to be used by | ||
/// [GoRouteInformationParser]. | ||
/// | ||
|
@@ -49,9 +56,16 @@ class RouteInformationState<T> { | |
this.completer, | ||
this.baseRouteMatchList, | ||
required this.type, | ||
MapRouteResultCallback<T?>? mapReplacementResult, | ||
}) : assert((type == NavigatingType.go || type == NavigatingType.restore) == | ||
(completer == null)), | ||
assert((type != NavigatingType.go) == (baseRouteMatchList != null)); | ||
assert((type != NavigatingType.go) == (baseRouteMatchList != null)), | ||
mapReplacementResult = | ||
mapReplacementResult ?? _defaultMapReplacementResult; | ||
|
||
static T? _defaultMapReplacementResult<T>(Object? result) { | ||
return null; | ||
} | ||
|
||
/// The extra object used when navigating with [GoRouter]. | ||
final Object? extra; | ||
|
@@ -63,13 +77,30 @@ class RouteInformationState<T> { | |
/// [NavigatingType.restore]. | ||
final Completer<T?>? completer; | ||
|
||
/// Maps the replacement result to the appropriate type, to resolve the | ||
/// [completer]. | ||
final MapRouteResultCallback<T?> mapReplacementResult; | ||
|
||
/// The base route match list to push on top to. | ||
/// | ||
/// This is only null if [type] is [NavigatingType.go]. | ||
final RouteMatchList? baseRouteMatchList; | ||
|
||
/// The type of navigation. | ||
final NavigatingType type; | ||
|
||
/// Pipes the completer to the next completer in the chain. | ||
Completer<Next?> pipeCompleter<Next extends Object?>(Completer<Next?> next) { | ||
if (completer == null) { | ||
return next; | ||
} | ||
|
||
return _PipeCompleter<T?, Next?>( | ||
next: next, | ||
current: completer!, | ||
mapReplacementResult: mapReplacementResult, | ||
); | ||
} | ||
} | ||
|
||
/// The [RouteInformationProvider] created by go_router. | ||
|
@@ -156,8 +187,12 @@ class GoRouteInformationProvider extends RouteInformationProvider | |
} | ||
|
||
/// Pushes the `location` as a new route on top of `base`. | ||
Future<T?> push<T>(String location, | ||
{required RouteMatchList base, Object? extra}) { | ||
Future<T?> push<T>( | ||
String location, { | ||
required RouteMatchList base, | ||
Object? extra, | ||
MapRouteResultCallback<T?>? mapReplacementResult, | ||
}) { | ||
final Completer<T?> completer = Completer<T?>(); | ||
_setValue( | ||
location, | ||
|
@@ -166,6 +201,7 @@ class GoRouteInformationProvider extends RouteInformationProvider | |
baseRouteMatchList: base, | ||
completer: completer, | ||
type: NavigatingType.push, | ||
mapReplacementResult: mapReplacementResult, | ||
), | ||
); | ||
return completer.future; | ||
|
@@ -196,8 +232,12 @@ class GoRouteInformationProvider extends RouteInformationProvider | |
|
||
/// Removes the top-most route match from `base` and pushes the `location` as a | ||
/// new route on top. | ||
Future<T?> pushReplacement<T>(String location, | ||
{required RouteMatchList base, Object? extra}) { | ||
Future<T?> pushReplacement<T>( | ||
String location, { | ||
required RouteMatchList base, | ||
Object? extra, | ||
MapRouteResultCallback<T?>? mapReplacementResult, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels weird, If we want to separate two use cases.
I think we should make two different API, maybe a pushReplacement for (1) and a pushReplaceResultFuture for (2), and in (2) we should force the type to be the same as previously pushed route. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. case (2) that match its originating type is guaranteed. this implementation makes the caller of the originating route is the one who's responsible for replacing future. for example, A is the one that responsible for replacing C result to match with B result type when it's pushing B. that's why
I do consider to make the I didn't consider case (1) before, it is indeed necessary to implement. but then, when routes can be resolve immediately when replaced, it needs value to return, and then again, we have no idea what type it should be, like pop is. |
||
}) { | ||
final Completer<T?> completer = Completer<T?>(); | ||
_setValue( | ||
location, | ||
|
@@ -206,14 +246,19 @@ class GoRouteInformationProvider extends RouteInformationProvider | |
baseRouteMatchList: base, | ||
completer: completer, | ||
type: NavigatingType.pushReplacement, | ||
mapReplacementResult: mapReplacementResult, | ||
), | ||
); | ||
return completer.future; | ||
} | ||
|
||
/// Replaces the top-most route match from `base` with the `location`. | ||
Future<T?> replace<T>(String location, | ||
{required RouteMatchList base, Object? extra}) { | ||
Future<T?> replace<T>( | ||
String location, { | ||
required RouteMatchList base, | ||
Object? extra, | ||
MapRouteResultCallback<T?>? mapReplacementResult, | ||
}) { | ||
final Completer<T?> completer = Completer<T?>(); | ||
_setValue( | ||
location, | ||
|
@@ -290,3 +335,42 @@ class GoRouteInformationProvider extends RouteInformationProvider | |
return SynchronousFuture<bool>(true); | ||
} | ||
} | ||
|
||
/// Ensures the replacement routes can resolve the originating route completer. | ||
/// Mainly used by [RouteInformationState.pipeCompleter] | ||
class _PipeCompleter<Current extends Object?, Next extends Object?> | ||
implements Completer<Next> { | ||
_PipeCompleter({ | ||
required Completer<Next> next, | ||
required Completer<Current> current, | ||
required MapRouteResultCallback<Current> mapReplacementResult, | ||
}) : _next = next, | ||
_current = current, | ||
_mapReplacementResult = mapReplacementResult; | ||
|
||
final Completer<Next> _next; | ||
final Completer<Current> _current; | ||
final MapRouteResultCallback<Current> _mapReplacementResult; | ||
|
||
@override | ||
void complete([FutureOr<Next>? value]) { | ||
_next.complete(value); | ||
if (!_current.isCompleted) { | ||
_current.complete(_mapReplacementResult(value)); | ||
} | ||
} | ||
|
||
@override | ||
void completeError(Object error, [StackTrace? stackTrace]) { | ||
_next.completeError(error, stackTrace); | ||
if (!_current.isCompleted) { | ||
_current.completeError(error, stackTrace); | ||
} | ||
} | ||
|
||
@override | ||
Future<Next> get future => _next.future; | ||
|
||
@override | ||
bool get isCompleted => _next.isCompleted; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.