Skip to content

Find an outer navigation controller by its scope - #2

Open
andrei-uni wants to merge 1 commit into
PlugFox:masterfrom
andrei-uni:feat/find-by-scope
Open

andrei-uni wants to merge 1 commit into
PlugFox:masterfrom
andrei-uni:feat/find-by-scope

Conversation

@andrei-uni

@andrei-uni andrei-uni commented Oct 26, 2026 •

Copy link
Copy Markdown

Adds a way to reach an outer navigator from a nested one.

Why

context.navigation and NavigationScope.of return only the closest controller. A screen inside a nested NavigationView (a sign in flow, a tab of a shell) cannot open or close a route in an outer view unless the outer controller is passed down by hand, through the fields of the routes or an inherited widget of the application.

Added

  • NavigationController.scope is an optional value that identifies the controller. It is compared with ==, so an enum value works.
  • NavigationScope.find and NavigationScope.maybeFind return the closest controller above the context with that scope.

Example

The tab controllers of the example get scope: tab. The cart tab gets a checkout: a route with a navigator of its own and two steps. The last step closes the whole checkout in the navigator of the tab, not only the step:

NavigationScope.find(context, scope: AppTab.cart).remove(Routes.checkout);

Behaviour

  • The search goes up the widget tree from the context.
    • The closest controller can match as well.
    • If several controllers share a scope, the closest one wins.
    • Views side by side each find their own controller.
  • A NavigationScope placed without a view is found too, the same way of finds it.
  • find throws a FlutterError when nothing matches, maybeFind returns null.
  • With listen: true the widget depends on the found controller only, not on the controllers between it and the context.

Design notes

  • find is a separate method, not a scope parameter of of. of keeps returning the closest controller, and a future root parameter of of (like Navigator.of(context, rootNavigator: true)) cannot be combined with a scope by mistake.
  • The scope belongs to the controller, not to the view:
    • the controller already owns the stack and its guards;
    • NavigationView and NavigationScope need no new parameters;
    • the value is fixed for the lifetime of the controller.
  • NavigationView.of and maybeOf are unchanged, and there is no new getter on BuildContext, in line with the README: an application adds its own shortcut in its extension type.
  • The search is a single walk over the ancestors, O(depth of the tree), the same as Navigator.of(context, rootNavigator: true).

Checks

  • make analyze: no format changes and no analyzer issues in the package or the example.
  • make test: package 230/230, example 12/12, line coverage 99.35%.
  • Flutter 3.35.0, the minimum in pubspec.yaml:
    • the formatter changes none of the 38 files;
    • no analyzer issues;
    • package 230/230, example 12/12.
  • The new package tests fail on a broken search, each of:
    • ignoring the scope;
    • skipping the closest controller;
    • taking the farthest match;
    • depending on the closest controller with listen: true.
  • The new example test fails if the last step uses the closest navigator instead of find.
  • flutter pub publish --dry-run: 0 warnings.

🤖 Generated with Claude Code

context.navigation and NavigationScope.of return only the closest
controller, so a screen inside a nested NavigationView could not open a
route in an outer view without passing the outer controller down by
hand.

- NavigationController.scope identifies a controller, compared with ==.
- NavigationScope.find and maybeFind return the closest controller above
  a context with the given scope, the closest controller included. find
  throws a FlutterError when there is none, maybeFind returns null.
- With listen: true the widget depends on the found controller only.
- The example gets a checkout in the cart tab: a nested navigator whose
  last step closes the whole flow in the navigator of the tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant