-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
[go_router] Add popUntil
#6306
base: main
Are you sure you want to change the base?
[go_router] Add popUntil
#6306
Conversation
int count = 0; // Only pop 1 page at the time. | ||
state?.popUntil((Route<dynamic> route) { | ||
if (count == 1 || predicate(walker, route)) { | ||
return true; | ||
} | ||
count++; | ||
return false; | ||
}); | ||
return state != null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I'm only calling predicate
once.
The reason is that after poping, the walker
might need to be re-computed in case the popped route was a route from go router.
Then why use popUntil
and not pop
if I pop only once? It is to be able to provide the Route<dynamic> route
parameter to the predicate. I cannot access the latest route of the NavigatorState
since NavigatorState._history
and NavigatorState._lastRouteEntryWhereOrNull
are private.
Do you have a better idea or suggestions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chunhtai Do you have any feedback on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a use case for Route<dynamic> route
? it seems a bit out of place that we all deal with RouteBase and GoRouterState in this package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The case I'm thinking about and I've been facing in some applications is when there are dialogs / bottom sheets / popup menus (or any other imperative routes) open
I also worked on some projects where the code base uses both go_router
and Navigator.of(context).push
. Either because they didn't get the time to migrate all the pages yet or because they don't want those pages to be linked to a specific URL on the web
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, my suggestion would be changing routematch to gorouterstate.
# Conflicts: # packages/go_router/CHANGELOG.md # packages/go_router/pubspec.yaml
@chunhtai @johnpryan @hangyujin , do you have any feedback on this PR? |
/// Signature for a predicate function that determines whether a route should be | ||
/// popped or not. | ||
typedef PopUntilPredicate = bool Function( | ||
RouteMatchBase, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RouteMatchBase is currently used internal only, we usually use GoRouterState for public facing API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure ! I changed it in refactor: Use GoRouterState instead of RouteMatchBase
int count = 0; // Only pop 1 page at the time. | ||
state?.popUntil((Route<dynamic> route) { | ||
if (count == 1 || predicate(walker, route)) { | ||
return true; | ||
} | ||
count++; | ||
return false; | ||
}); | ||
return state != null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a use case for Route<dynamic> route
? it seems a bit out of place that we all deal with RouteBase and GoRouterState in this package.
# Conflicts: # packages/go_router/CHANGELOG.md # packages/go_router/pubspec.yaml
/// Pop the Navigator's page stack until the predicate returns `true`. | ||
void popUntil(PopUntilPredicate predicate) { | ||
bool hasStoppedPopping = false; | ||
bool popUntilPredicate(GoRouterState state, Route<dynamic> route) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not entirely sure why this method is needed. it seems to me that you can use predicate
directly instead of wrapping in a method that doesn't do much
} | ||
|
||
while (!hasStoppedPopping) { | ||
final bool couldBePopped = _popWithPredicate(popUntilPredicate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not just let _popWithPredicate
to return true if the predicate after pop return false? an then you can basically do
while(_popWithPredicate(popUntilPredicate));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something like this
void popUntil(PopUntilPredicate predicate) {
while (_popWithPredicate(predicate)) {}
}
bool _popWithPredicate(
PopUntilPredicate predicate,
) {
NavigatorState? state;
if (navigatorKey.currentState?.canPop() ?? false) {
state = navigatorKey.currentState;
}
List<RouteMatchBase> matches = currentConfiguration.matches;
while (matches.last is ShellRouteMatch) {
final ShellRouteMatch leaf = matches.last as ShellRouteMatch;
if (leaf.navigatorKey.currentState?.canPop() ?? false) {
state = leaf.navigatorKey.currentState;
}
matches = leaf.matches;
}
bool predicateMatched = false;
state?.popUntil((Route<dynamic> route) {
final RouteMatchBase leaf = matches.last;
if (!state!.canPop() || leaf is ShellRouteMatch) {
// If the leaf is ShellRouteMatch, it needs to pop leaf's navigator
// first.
return true;
}
predicateMatched = predicate(leaf.buildState(_configuration, currentConfiguration), route,);
return predicateMatched;
});
return state != null && !predicateMatched;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of refactor: Simplify the code by removing a method ?
If that's fine I can go with it and work on adding more tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes that looks fine
@@ -4770,6 +4770,62 @@ void main() { | |||
expect(find.byKey(heroKey), findsNothing); | |||
}); | |||
}); | |||
|
|||
group('popUntil', () { | |||
testWidgets( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably have more tests on cases where nested shellroute.
Hi @ValentinVignal , are you still working on this one? |
Yes, but I'm trying to finish |
converting to draft for now, please convert back to pr once it is ready |
# Conflicts: # packages/go_router/CHANGELOG.md # packages/go_router/pubspec.yaml
Hi @ValentinVignal is this ready for another look? |
@chunhtai I left you a message here, I was waiting for your feed back |
@chunhtai I'm facing an issue with It successfully pops packages/packages/go_router/lib/src/match.dart Lines 686 to 693 in e95fe4a
with But Because of that
is false and the Looking at the code, the outdated packages/packages/go_router/lib/src/builder.dart Lines 413 to 417 in e95fe4a
The packages/packages/go_router/lib/src/builder.dart Lines 419 to 425 in e95fe4a
but there is no build run while using This strategy of updating the state in the build method looks suspicious to me, I guess this is to provide a valid Do you have any suggestions on what we could do here? |
@chunhtai Do you have some feedback on this? |
# Conflicts: # packages/go_router/CHANGELOG.md
Part of flutter/flutter#144924
Closes flutter/flutter#131625
Pre-launch Checklist
dart format
.)[shared_preferences]
pubspec.yaml
with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.CHANGELOG.md
to add a description of the change, following repository CHANGELOG style.///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.