Skip to content
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 support for preloading branches of StatefulShellRoute (revised solution) #6467

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

tolo
Copy link

@tolo tolo commented Apr 5, 2024

Adds support for preloading branches in a StatefulShellRoute. This functionality was initially part of an early implementation of #2650, however it was decided to implement this in a separate PR. The current implementation is a rewrite of the original implementation to better fit the final version of StatefulShellRoute (and go_router in general).

NOTE: this is a revised version of the initial solution (see #4251), containing a substantially simpler implementation made possible thanks to recent refactoring in go_router.

This fixes issue flutter/flutter#127804.

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@chunhtai
Copy link
Contributor

I know I have been slacking off at this pr. I am sorry about this. My schedule is quite tight after I came back from vacation. This is still under my radar. I will try find time review this pr within 2 weeks

@tolo
Copy link
Author

tolo commented Apr 30, 2024

I know I have been slacking off at this pr. I am sorry about this. My schedule is quite tight after I came back from vacation. This is still under my radar. I will try find time review this pr within 2 weeks

No worries @chunhtai, I completely understand, have had far too much on my plate myself the last year 😅

return true;
});

final Widget navigator = widget.shellRouteContext.navigatorBuilder(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might want to null check match here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, will update this.

RouteMatchList matchList,
List<NavigatorObserver>? observers,
String? restorationScopeId);
// typedef NavigatorBuilder = Widget Function(
Copy link
Contributor

@cedvdb cedvdb May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like this commented code can be removed

@@ -60,6 60,9 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
],
),
],
// To enable preloading of the initial locations of branches, pass
// 'true' for the parameter preload.
// preload: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd find it clearer if the last line was uncommented as preload: false

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may be right, I'll consider this.

/// *Note:* The primary purpose of branch preloading is to enhance the user
/// experience when switching branches, which might for instance involve
/// preparing the UI for animated transitions etc. Care must be taken to
/// **keep the preloading to an absolute minimum** to avoid any unnecessary
Copy link
Contributor

@cedvdb cedvdb May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The position here is overly dramatic imo. I'd change

 Care must be taken to **keep the preloading to an absolute minimum** to avoid any unnecessary resource use.

into

 Preloading involves resource usage for elements that are not displayed, as such this should be used sparingly.

or something like that.

Personally I'm going to use that for every top level branch, which I have 5 of. That's not the absolute minimum. Anyway, I just find the wording too strong and might scare some people from using an otherwise useful feature. People can benchmark the effect.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great feedback. This wording was actually due to some rather lengthy discussion in the original PR around the potential negative aspects of preloading, as I recall it. There are always dangers of premature/unnecessary optimisation when exposing these kinds of APIs, so some level of cautionary language is certainly justified. But maybe it can be dialed back a bit.

@unacorbatanegra
Copy link

Any updates on this?

Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, just some comments

@@ -996,6 1011,7 @@ class StatefulShellBranch {
this.initialLocation,
this.restorationScopeId,
this.observers,
this.preload = false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we check for initiallocation is set if preload is true?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think that should be necessary, as it will fall back on the defaultRoute if initialLocation isn't provided.

@@ -1032,6 1048,21 @@ class StatefulShellBranch {
/// The observers parameter is used by the [Navigator] built for this branch.
final List<NavigatorObserver>? observers;

/// Whether this route branch should be loaded only when navigating to it for
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you inverse the statement? from reading it, it seem to not preload when this property is true

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, forgot to change that. How about this: "Whether this route branch should be eagerly loaded when navigating to it for the first time..."?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's also confusing, it's not preloading when accessing the branch for the first time but when accessing the shell.

"Whether this "StatefulShellBranch" should be preloaded once the "StatefulShellRoute" has been accessed."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, but of course, how about this:
"Whether this route branch should be eagerly loaded when navigating to the associated StatefulShellRoute for the first time."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's much better

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes sounds good to me, can you reflect that to the doc string?

@@ -1255,8 1291,39 @@ class StatefulNavigationShellState extends State<StatefulNavigationShell>
final bool locationChanged =
previousBranchLocation != currentBranchLocation;
if (locationChanged || !hasExistingNavigator) {
_branchNavigators[branch.navigatorKey] = shellRouteContext
.navigatorBuilder(branch.observers, branch.restorationScopeId);
_branchNavigators[branch.navigatorKey] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just realized somewhere in this method should probably remove entries in _branchNavigators the are no longer in the StatefulShellRoute?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, that is definitely necessary to do now. Will have a look at it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chuntai, I added a fix for removing old entries, but when attempting to write a test case, I ran into issues with duplicate GlobalKeys (_shellStateKey) when updating the dynamic RoutingConfig. Or - I should be clear - at first the test case worked, but not because of the code I added, but instead because the entire StatefulShellRoute was replaced/reloaded. But when I added the possibility to set the GlobalKey<StatefulNavigationShellState> _shellStateKey to maintain state, I started to get this error.

When trying to troubleshoot it, I started to wonder whether dynamic RoutingConfig full supports ShellRoutes, at least with state. Added some test cases to test this, and it seems simple StatefulWidgets in a simple routing scenario works, but when adding a (regular) ShellRoute with a stateful shell I also get and issue with duplicate GlobalKeys.

Not sure if I just missed something, but I anyway added a gist with the test cases here: https://gist.github.com/tolo/922fd838cdea30b17121533b0012eda4

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you file an issue for it? looks like a bug

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the fix in this pr already?

@cedvdb
Copy link
Contributor

cedvdb commented Jul 3, 2024

What's the status here ? It seems like you said that you have a test locally that's failing but it's unrelated to the changes here ?

@tolo
Copy link
Author

tolo commented Jul 10, 2024

What's the status here ? It seems like you said that you have a test locally that's failing but it's unrelated to the changes here ?

Yes, @chunhtai, have you had a chance to too look at my comment above (#6467 (comment))?

The issue is however only with testing if obsolete branch navigators are removed (which is done dynamic RoutingConfig), so possibly this could be moved to a separate issue/PR. And another issue should probably be opened for fixing the support for dynamic RoutingConfig for (stateful) shell routes. What do you think @chunhtai?

@chunhtai
Copy link
Contributor

will take a look this week

routes: <RouteBase>[
StatefulShellRoute(
StatefulShellRoute.indexedStack(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why changing this example? In stead of adding more complexity to this example, we should probably create a new example that target specific feature

@@ -1032,6 1048,21 @@ class StatefulShellBranch {
/// The observers parameter is used by the [Navigator] built for this branch.
final List<NavigatorObserver>? observers;

/// Whether this route branch should be loaded only when navigating to it for
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes sounds good to me, can you reflect that to the doc string?

@@ -1255,8 1291,39 @@ class StatefulNavigationShellState extends State<StatefulNavigationShell>
final bool locationChanged =
previousBranchLocation != currentBranchLocation;
if (locationChanged || !hasExistingNavigator) {
_branchNavigators[branch.navigatorKey] = shellRouteContext
.navigatorBuilder(branch.observers, branch.restorationScopeId);
_branchNavigators[branch.navigatorKey] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you file an issue for it? looks like a bug

@@ -1255,8 1291,39 @@ class StatefulNavigationShellState extends State<StatefulNavigationShell>
final bool locationChanged =
previousBranchLocation != currentBranchLocation;
if (locationChanged || !hasExistingNavigator) {
_branchNavigators[branch.navigatorKey] = shellRouteContext
.navigatorBuilder(branch.observers, branch.restorationScopeId);
_branchNavigators[branch.navigatorKey] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the fix in this pr already?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants