-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[go_router] When replace
is called, reuse the same key when possible
#2846
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] When replace
is called, reuse the same key when possible
#2846
Conversation
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 think the push and replace is different enough that we should probably not reuse push in replace method, or have a common private method that just take a non-null page key.
final ValueKey<String> pageKey = | ||
ValueKey<String>('${matches.fullpath}-p$count'); | ||
final PageKey pageKey; | ||
if (keyToUse?.path == matches.fullpath) { |
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 do we need this check? why not just use keyToUse if it is not 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.
Because if the page is different and the key doesn't match, ex: /family/:fid
and /family
, it shouldn't reuse it, right?
I could have named keyToUse
keyToReuseIfPossible
to be more explicit but it was a bit long
@chunhtai I refactored |
push(matches); // [push] will notify the listeners. | ||
assert(matches.last.route is! ShellRoute); | ||
PageKey pageKey = _matchList.pop().pageKey; | ||
if (pageKey.path != matches.fullpath) { |
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.
Seems weird to sometimes reuse and sometimes push. I am not 100% sure this is the route we should go. I was looking at the navigator API, it looks like this mixes both Navigator.replace and Navigator.pushReplacement. Based on the implemented API, this is more close to pushReplacement
. In that case we should probably always trigger a push, and create another API to do the replace
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.
Are you suggesting that we implement a
pushReplacement
that always uses a new keyreplace
that always reuses the key of the replaced page
?
If yes, how would you want me to name those methods? pushReplacement
and replace
too? I don't know whether it will be considered as a breaking change or not
And in the case of /a
being replaced by /b
with replace
, would it make sense to reuse /a
s key and see /b
's screen while using /a
as a key?
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 think we should probably rename this to pushReplacement and always does a push. I think implementing replace is a bit risky because we need to worry about people replacing shellRoute. I would probably leave it out for now until we figure out these corner cases
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'm okay to rename the current replace
into pushReplacement
.
But how do I handle that? Do I make it a breaking change? Or do I deprecate replace
and make it return pushReplacement
?
Also, I guess you want this pushReplacement
to already generate and use a brand new key, right?
The fix needed to fix flutter/flutter#115902 is to reuse the key when possible
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 will leave the original issue as a feature request, but I don't think that is something we should do.
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.
Do you mean you want to leave replace
(and reuse the same key) as a feature request but it would not be implemented?
How would suggest I can fix the bug mentioned in flutter/flutter#115902?
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 can see a lot of corner cases if we implement this API, if you have an idea on how to implement it, feel free to send a pr
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 about something similar to what I implemented in this PR?
It would reuse the same key if the path (ex: /families/:fid
) is the same. Otherwise, it creates a new key?
I can modify this PR once #2848 is merged
Please add the option which displays depreciated with the new term in VSCode and Android Studio when hovering over |
9b5769c
to
f85f785
Compare
@chunhtai What do you think about this implementation? |
It looks like the checks are failing because of |
We decide to keep named location since we couldn't come up with a better alternative, and functionality wise it works fine. |
Ok I can add |
# Conflicts: # packages/go_router/CHANGELOG.md # packages/go_router/lib/src/delegate.dart # packages/go_router/lib/src/match.dart # packages/go_router/lib/src/matching.dart # packages/go_router/pubspec.yaml
I implemented |
@MarufHassan17, I'm not sure I understand what you mean? Did you mean I should add the |
@chunhtai What do you think of this implementation? Or do you think the key should always be reused when the user calls
What do you think? |
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.
sorry for the late reply, I just come back from vacation.
_matchList.remove(_matchList.last); | ||
push(matches); // [push] will notify the listeners. | ||
} | ||
|
||
/// Replaces the top-most page of the page stack with the given one and reuse |
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.
This doc should not talk about page key since that is implementation detail. Instead it should talk about the visual effect of calling this method. Same thing for pushReplacement
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.
This seems not addressed yet?
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 I could write in the doc that pushReplacement
animates to the new page while replace
does not.
But it is not only that, pushReplacement
doesn't preserve the state of the widget tree while replace
does. I think this is an important aspect that needs to be said and that's why I'm mentioning the page key.
Having said this, what do you think I should write?
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.
You should definitely mention animation/page transition and that the it will be treated as same page and state will be preserved. I am ok if either you keep the page key info here or not.
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.
This doesn't seem to be addressed yet
/// See also: | ||
/// * [push] which pushes the given location onto the page stack. | ||
/// * [pushReplacement] which replaces the top-most page of the page stack but | ||
/// always use a new page key. |
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.
same here
/// * [push] which pushes the given location onto the page stack. | ||
/// * [pushReplacement] which replaces the top-most page of the page stack but | ||
/// always use a new page key. | ||
void replace(RouteMatchList matches) { |
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.
This method starts to deviate from Navigator.replace, which can replace any route instead of just the top one. One thing I think that may be useful would be replace GoRoute with an other GoRoute. maybe we can use GoRoute.name as here? WDYT?
also cc @johnpryan for API feedback
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'm not trying to re-implement Navigator.replace
API here. The main goal of this PR is to fix flutter/flutter#115902. The solution I came up with was this replace
method (which we could name differently, ex: replaceLast
). I don't know whether re-implementing Navigator.replace
would fix the issue (maybe it will not re-use the same key, like pushReplacement
) or not.
Having this in mind, what do you think we should do in this PR?
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 think the key only matter when replacing the top-most page?
My worry is that someone later on may file another feature request to replace the middle of match list, and then we would need to come up with a new api since the function signature here cannot be reused.
I also been thinking about opening up the RouteMatchList and all the package level private class. That may open a better way to manage the RouteMatchList.
So I think there are two ways to go from here. Either we expand this to be able to replace any GoRoute(or a sub list of RouteBases) with one or more RouteBases. Or we open up the API now and let customers to do their things.
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.
So I think there are two ways to go from here. Either we expand this to be able to replace any GoRoute(or a sub list of RouteBases) with one or more RouteBases. Or we open up the API now and let customers to do their things.
Is there a way you prefer over the other? I guess I can try to implement the replace
method here
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.
Hello @chunhtai I tried to look a bit at how it could be possible to make replace
a bit like
void Navigator.replace({ required Route<dynamic> oldRoute, required Route<T> newRoute})
So I guess, from what you said:
One thing I think that may be useful would be replace GoRoute with an other GoRoute. maybe we can use GoRoute.name as here
replace
could have one of the signatures (if routeNameToReplace
is null
, it would replace the top most route).
void replace(RouteMatchList matchese, {String? routeNameToReplace});
void replace(RouteBase newRoute, {String? routeNameToReplace});
There are several issues with this:
- If a route is present multiple times in the history, which one is supposed to be replaced?
- This solution doesn't work with
go_router_builder
as all the generated routes are nameless.
About your concerns
My worry is that someone later on may file another feature request to replace the middle of match list, and then we would need to come up with a new api since the function signature here cannot be reused.
The signature of the method replace
of the delegate could be changed easily as it is not opened yet, so there should not be any issues there.
The signature of the method replace
of the router should not be breaking. It could change from
void replace(String location, {Object? extra});
to
void replace(String location, {WhateverType? whateverParameter, Object? extra});
What do you think?
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 still slightly concern about using a string location to do the replace, but i can't find a better alternative. yeah, I am ok with this approach
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.
please re-request me for review when this pr is ready.
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.
yeah, I am ok with this approach
What approach are you okay with? The one currently implemented in this PR ? (If yes, it would be ready to review)
If you are talking about
void replace(RouteMatchList matchese, {String? routeNameToReplace});
void replace(RouteBase newRoute, {String? routeNameToReplace});
What about
There are several issues with this:
- If a route is present multiple times in the history, which one is supposed to be replaced?
- This solution doesn't work with go_router_builder as all the generated routes are nameless.
?
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 current implementation
…e-key-when-replace # Conflicts: # packages/go_router/CHANGELOG.md # packages/go_router/pubspec.yaml
…e-key-when-replace # Conflicts: # packages/go_router/CHANGELOG.md # packages/go_router/pubspec.yaml
@chunhtai When you review, could you take a look at #2846 (comment)
I'm not sure which implementation would be best |
@chunhtai I updated the doc in docs: Update the doc of replace to mention animation and state Tell me if it is good enough for you |
@@ -89,4 +99,29 @@ extension GoRouterHelper on BuildContext { | |||
queryParams: queryParams, | |||
extra: extra, | |||
); | |||
|
|||
/// Replaces the top-most page of the page stack with the given one. |
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.
Can you also mention the transition and preserve state behavior somewhere in here as well?
@@ -272,6 +282,45 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> { | |||
); | |||
} | |||
|
|||
/// Replaces the top-most page of the page stack with the given one. | |||
/// |
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 as well
|
||
/// Replaces the top-most page of the page stack with the named route w/ | ||
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid': | ||
/// 'p1'}`. |
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 as well
@chunhtai Sorry for the misses, I added the doc in docs: Add more doc to replace and replaceNamed |
@stuartmorgan @ditman can you help us figure out why CI is failing? |
@johnpryan "submit-queue" is the check that asserts that whatever is at the tip of flutter/packages main branch is passing all post-submit tests. It looks red now, because flutter/packages main is undergoing testing (here), but once that finishes correctly, it'll turn green in this PR as well. |
…e-key-when-replace # Conflicts: # packages/go_router/CHANGELOG.md # packages/go_router/pubspec.yaml
Updated branch to fix merge conflicts. @chunhtai I've seen you've made some comments after LGTMing. Please, take another look, and if you're still good with this landing, set the |
…e-key-when-replace # Conflicts: # packages/go_router/CHANGELOG.md
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.
some of the doc is still missing
/// Replaces the top-most page of the page stack with the given one. The page | ||
/// key of the new page will always be different from the old one. |
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.
/// Replaces the top-most page of the page stack with the given one. The page | |
/// key of the new page will always be different from the old one. | |
/// Replaces the top-most page of the page stack with the given one. | |
/// | |
/// The page key of the new page will always be different from the old one. |
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.
Sorry for that. Hopefully docs: Improve the documentation addressed all your comments
_matchList.remove(_matchList.last); | ||
push(matches); // [push] will notify the listeners. | ||
} | ||
|
||
/// Replaces the top-most page of the page stack with the given one and reuse |
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.
This doesn't seem to be addressed yet
@@ -272,6 +282,48 @@ class GoRouter extends ChangeNotifier implements RouterConfig<RouteMatchList> { | |||
); | |||
} | |||
|
|||
/// Replaces the top-most page of the page stack with the given one but treats | |||
/// it as the same page. The page key will be reused. This will preserve the |
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 first paragraph should always be one brief sentence
}); | ||
} | ||
|
||
/// Replaces the top-most page of the page stack with the named route w/ |
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.
/// Replaces the top-most page of the page stack with the named route w/ | |
/// Replaces the top-most page of the page stack with the named route with |
|
||
/// Replaces the top-most page of the page stack with the named route w/ | ||
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid': | ||
/// 'p1'}`. But it treats it as the same page. The page key will be reused. |
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 first paragraph should be one sentence
@@ -89,4 +99,32 @@ extension GoRouterHelper on BuildContext { | |||
queryParams: queryParams, | |||
extra: extra, | |||
); | |||
|
|||
/// Replaces the top-most page of the page stack with the given one but treats | |||
/// it as the same page. The page key will be reused. This will preserve the |
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.
same
|
||
/// Replaces the top-most page of the page stack with the named route w/ | ||
/// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid': | ||
/// 'p1'}`. But it treats it as the same page. The page key will be reused. |
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.
same
…e-key-when-replace # Conflicts: # packages/go_router/CHANGELOG.md
@chunhtai @Hangyujin is there anything else I need to do ? |
@chunhtai @Hangyujin If the code looks good to you, do you think we can go through with this PR? |
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.
LGTM
@chunhtai @Hangyujin , the check |
@ValentinVignal no that means the current tip of tree is broken, it would be us to fix it. No action item on your side. |
…e-key-when-replace
auto label is removed for flutter/packages, pr: 2846, due to - The status or check suite Windows win32-platform_tests stable - packages has failed. Please fix the issues identified (or deflake) before re-applying this label. |
flutter#2846) [go_router] When `replace` is called, reuse the same key when possible
Should fix flutter/flutter#115902
I've introduced
PageKey
to be used as a key for the pages instead ofValueKey<String>
. In that way, it is easy to know if 2 keys are from the same path or not.I'm not too confident about my changes in
packages/go_router/lib/src/match.dart
: It uses thehashCode
packages/go_router/lib/src/builder.dart
: This is the only place I had to usefullPath
. So maybe we could usepath
directly?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.